Repository navigation
feat(harness-sdk): the kilo harness SDK - #5878
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryNo new issues: the PR-owned Files Reviewed (6 files)
Incremental scope
Previous Review Summaries (10 snapshots, latest commit 4c7df0a)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 4c7df0a)Status: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous review (commit 1b6d23f)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (14 files)
Fix these issues in Kilo Cloud Previous review (commit b1600c9)Status: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous review (commit c5808bf)Status: No Issues Found | Recommendation: Merge Files Reviewed (6 files)
Previous review (commit 7556919)Status: No Issues Found | Recommendation: Merge Files Reviewed (1 files)
Previous review (commit 7dc528e)Status: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous review (commit 7fe69c0)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (4 files)
Fix these issues in Kilo Cloud Previous review (commit 4766987)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (5 files)
Fix these issues in Kilo Cloud Previous review (commit dfc1b09)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (9 files)
Fix these issues in Kilo Cloud Previous review (commit 1f53424)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (7 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
2e6d972 to
1b6d23f
Compare
|
bot: Accepted. Pushed 4c7df0a (kwf What changed:
|
1b6d23f to
4c7df0a
Compare
eshurakov
left a comment
There was a problem hiding this comment.
Approved after a shallow triage pass. No blocking findings.
# Conflicts: # .github/workflows/ci.yml # pnpm-lock.yaml
The merge rebuilt the lockfile from the branch side, so pnpm resolved again every package main had added since. That moved @peculiar/asn1-* to 2.9.5 and broke the passkey verification tests in apps/web. The lockfile is now main's, with only the harness SDK's own entries added.
# Conflicts: # .github/workflows/ci.yml # pnpm-lock.yaml
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…uick Chat (#7338) * feat(harness-sdk): the kilo harness SDK #5878 * feat(mobile): rebuild the chat tab on the harness SDK #5895 * chore(kilo-mcp): refresh catalog.json from CI * feat(mobile): connect chat to the Kilo MCP server with the signed-in session (#6420) #6420 * chore: publish the verified app-agent settings tools + remote MCP stack as one PR (level 1) * fix(mobile): preserve settings and remote MCP state across cold loads * fix(mobile): satisfy async and control-flow lint rules * fix(harness-sdk): keep the library's abort link until the operation ends fetch resolves when the headers arrive, so the per-request unlink let close() leave an open stream running until the deadline timer fired. The deadline now holds each joined signal once and removes it at stop. * fix(harness-sdk): bound the credential by the operation's deadline The token was asked before the deadline existed, so a slow refresh could hold a chat open past the discovery bound. The deadline now comes first and the credential races it. * fix(harness-sdk): give a call a deadline above the time it is waited on inline A remote tool is waited on for 15 s before the session backgrounds it, and the call's own default deadline was also 15 s, so a backgrounded call failed at that moment instead of answering. A call now defaults to 60 s; discovery keeps 15 s. * fix(mobile): publish the Kilo tools answer that a cache hit serves The registry builds a chat from the snapshot, not from the return value. A cache hit returned the ready answer while another scope's discovery had published connecting over it, so the chat opened on the base tools. * fix(mobile): keep each Kilo tools discovery on its caller's deadline A caller that joined a running discovery took the first caller's deadline, so an open that met a Retry waited fifteen seconds and a Retry that met an open was cut to four. A caller now joins only a discovery with its own deadline, and only the latest discovery is published. * fix(mobile): lock the Kilo tools switch when there is nothing to use In the unavailable state the switch read off while the chat's setting was on, so a tap wrote the value already stored and the switch snapped back. The view now says whether the switch can move, and the sheet disables it when not. * fix(deps): rebuild the lockfile on main's pins The merge rebuilt the lockfile from the branch side, so pnpm resolved again every package main had added since. That moved @peculiar/asn1-* to 2.9.5 and broke the passkey verification tests in apps/web. The lockfile is now main's, with only the harness SDK's own entries added. * fix(mobile): pass the segments to the toaster's tab bar check Main added a shouldHideTabBar call in app-root-providers.tsx with the old one-argument form. This branch reads the chat conversation from the segments, so the call now passes them too. * test(mobile): read the hoisted query client mock in the account switch test Main's lifecycle file already holds queryClientMock at module scope. The branch's test declared it again from a dynamic import, which lint refuses as a shadow and as an unbound method. * fix(mobile): describe the spend alerts notification setting Main added the spendAlerts notification category. The agent settings catalog maps every category to a description, so typecheck failed on the missing one. * fix(mobile): use the shared MCP servers copy in the chat sheet Main added profiles.mcp.title and profiles.mcp.add with the same English copy as modelChat.mcp.serversSection and modelChat.mcp.addServer, and the catalog check refuses two keys for one copy. The chat section now reads the profile keys, and the two chat keys are removed from every catalog. * test(mobile): load the real config module in the MCP URL test vitest.setup.ts now stubs @/lib/config for every pure suite, so the plain dynamic import in config.test.ts returned the stub and KILO_MCP_URL read as undefined. The test now loads the module through vi.importActual. * test(mobile): allow the remote MCP id slug in the case guard slugFor in remote-mcp-store.ts lowercases a server name to build the callable tool-name id. The id is never shown, so the file joins the non-display list. * feat(mobile): support configurable Quick Chat backends * test(harness): construct synthetic credential URL explicitly * fix(mobile): address Quick Chat backend review findings * fix(mobile): reopen a failed chat from Retry * test(mobile): allow protocol-identifier case normalization in backend files * fix(mobile): wait for stored backends before the send check * feat(mobile): add on-device Apple Foundation Models to Quick Chat * feat(mobile): add on-device Gemini Nano to Quick Chat on Android * fix(mobile): reference the Retry label in the on-device background copy * feat(mobile): add llama.rn GGUF models to Quick Chat * style(mobile): format the on-device GGUF files * test(mobile): allow the GGUF file-extension comparison in the case guard * fix(mobile): keep unreadable model entries from deleting downloads * test(mobile): split the model-index policy tests into their own suite * fix(mobile): never rewrite a model list holding an unreadable entry * fix(mobile): keep any list that did not parse in full from being rewritten * fix(mobile): read a GGUF result that carries nulls or only text * fix(mobile): treat a null parsed content as absent * test(mobile): pin the null-heavy llama.rn result at the client level * feat(mobile): open message details and recover a failed question in Quick Chat A long-press on a Quick Chat message opens the message details sheet. The unanswered question carries Retry and Copy to composer inline. A failed answer reads as a failed response, not as a stopped message. The chat row under the transcript states the reason and keeps its Retry only when no question waits for an answer. Quick Chat messages carry no model and no time. A model switch copies every turn into a new session, so the details sheet omits those rows. * fix(mobile): drop the duplicate llama.rn proguard rule The merge of main added a second extraProguardRules next to main's combined rule, which app.config.ts rejects as a duplicate key. Keep main's combined rule. * ci(trufflehog): exclude the synthetic-credential test fixture security.test.ts builds a credential URL from the literal 'user:private-key@provider.example'. The commit that added it stays in this branch's history, so trufflehog reports it on every base..head scan even though a later commit replaced it with a constructed value. Exclude the fixture path. * fix(db): drop the stale duplicate 0263_drop_quick_chat migration main lands the drop as 0274_drop_quick_chat.sql (registered in the journal). The same drop sits in this branch's lineage as 0263_drop_quick_chat.sql, unregistered and colliding with 0263_spend_alerts_rollup_covering_index. The merge kept it because main never had that path. Remove it; the tree now matches main. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(harness-sdk): the kilo harness SDK #5878 * feat(mobile): rebuild the chat tab on the harness SDK #5895 * chore(kilo-mcp): refresh catalog.json from CI * feat(mobile): connect chat to the Kilo MCP server with the signed-in session (#6420) #6420 * chore: publish the verified app-agent settings tools + remote MCP stack as one PR (level 1) * fix(mobile): preserve settings and remote MCP state across cold loads * fix(mobile): satisfy async and control-flow lint rules * fix(harness-sdk): keep the library's abort link until the operation ends fetch resolves when the headers arrive, so the per-request unlink let close() leave an open stream running until the deadline timer fired. The deadline now holds each joined signal once and removes it at stop. * fix(harness-sdk): bound the credential by the operation's deadline The token was asked before the deadline existed, so a slow refresh could hold a chat open past the discovery bound. The deadline now comes first and the credential races it. * fix(harness-sdk): give a call a deadline above the time it is waited on inline A remote tool is waited on for 15 s before the session backgrounds it, and the call's own default deadline was also 15 s, so a backgrounded call failed at that moment instead of answering. A call now defaults to 60 s; discovery keeps 15 s. * fix(mobile): publish the Kilo tools answer that a cache hit serves The registry builds a chat from the snapshot, not from the return value. A cache hit returned the ready answer while another scope's discovery had published connecting over it, so the chat opened on the base tools. * fix(mobile): keep each Kilo tools discovery on its caller's deadline A caller that joined a running discovery took the first caller's deadline, so an open that met a Retry waited fifteen seconds and a Retry that met an open was cut to four. A caller now joins only a discovery with its own deadline, and only the latest discovery is published. * fix(mobile): lock the Kilo tools switch when there is nothing to use In the unavailable state the switch read off while the chat's setting was on, so a tap wrote the value already stored and the switch snapped back. The view now says whether the switch can move, and the sheet disables it when not. * fix(deps): rebuild the lockfile on main's pins The merge rebuilt the lockfile from the branch side, so pnpm resolved again every package main had added since. That moved @peculiar/asn1-* to 2.9.5 and broke the passkey verification tests in apps/web. The lockfile is now main's, with only the harness SDK's own entries added. * fix(mobile): pass the segments to the toaster's tab bar check Main added a shouldHideTabBar call in app-root-providers.tsx with the old one-argument form. This branch reads the chat conversation from the segments, so the call now passes them too. * test(mobile): read the hoisted query client mock in the account switch test Main's lifecycle file already holds queryClientMock at module scope. The branch's test declared it again from a dynamic import, which lint refuses as a shadow and as an unbound method. * fix(mobile): describe the spend alerts notification setting Main added the spendAlerts notification category. The agent settings catalog maps every category to a description, so typecheck failed on the missing one. * fix(mobile): use the shared MCP servers copy in the chat sheet Main added profiles.mcp.title and profiles.mcp.add with the same English copy as modelChat.mcp.serversSection and modelChat.mcp.addServer, and the catalog check refuses two keys for one copy. The chat section now reads the profile keys, and the two chat keys are removed from every catalog. * test(mobile): load the real config module in the MCP URL test vitest.setup.ts now stubs @/lib/config for every pure suite, so the plain dynamic import in config.test.ts returned the stub and KILO_MCP_URL read as undefined. The test now loads the module through vi.importActual. * test(mobile): allow the remote MCP id slug in the case guard slugFor in remote-mcp-store.ts lowercases a server name to build the callable tool-name id. The id is never shown, so the file joins the non-display list. * feat(mobile): support configurable Quick Chat backends * test(harness): construct synthetic credential URL explicitly * fix(mobile): address Quick Chat backend review findings * fix(mobile): reopen a failed chat from Retry * test(mobile): allow protocol-identifier case normalization in backend files * fix(mobile): wait for stored backends before the send check * feat(mobile): add on-device Apple Foundation Models to Quick Chat * feat(mobile): add on-device Gemini Nano to Quick Chat on Android * fix(mobile): reference the Retry label in the on-device background copy * feat(mobile): add llama.rn GGUF models to Quick Chat * style(mobile): format the on-device GGUF files * test(mobile): allow the GGUF file-extension comparison in the case guard * fix(mobile): keep unreadable model entries from deleting downloads * test(mobile): split the model-index policy tests into their own suite * fix(mobile): never rewrite a model list holding an unreadable entry * fix(mobile): keep any list that did not parse in full from being rewritten * fix(mobile): read a GGUF result that carries nulls or only text * fix(mobile): treat a null parsed content as absent * test(mobile): pin the null-heavy llama.rn result at the client level * fix(mobile): remove the mode chip from the Quick Chat composer Quick Chat is not an agent session, so modes do not apply. The toolbar and the composer take one optional modeControl object. Without it, the toolbar draws no mode chip and the model chip starts the row. * test(mobile): pin the mode control on the session composer * fix(mobile): drop the duplicate llama.rn proguard rule The merge of main added a second extraProguardRules next to main's combined rule, which app.config.ts rejects as a duplicate key. Keep main's combined rule. * ci(trufflehog): exclude the synthetic-credential test fixture security.test.ts builds a credential URL from the literal 'user:private-key@provider.example'. The commit that added it stays in this branch's history, so trufflehog reports it on every base..head scan even though a later commit replaced it with a constructed value. Exclude the fixture path. * fix(db): drop the stale duplicate 0263_drop_quick_chat migration main lands the drop as 0274_drop_quick_chat.sql (registered in the journal). The same drop sits in this branch's lineage as 0263_drop_quick_chat.sql, unregistered and colliding with 0263_spend_alerts_rollup_covering_index. The merge kept it because main never had that path. Remove it; the tree now matches main. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(harness-sdk): the kilo harness SDK #5878 * feat(mobile): rebuild the chat tab on the harness SDK #5895 * chore(kilo-mcp): refresh catalog.json from CI * feat(mobile): connect chat to the Kilo MCP server with the signed-in session (#6420) #6420 * chore: publish the verified app-agent settings tools + remote MCP stack as one PR (level 1) * fix(mobile): preserve settings and remote MCP state across cold loads * fix(mobile): satisfy async and control-flow lint rules * fix(harness-sdk): keep the library's abort link until the operation ends fetch resolves when the headers arrive, so the per-request unlink let close() leave an open stream running until the deadline timer fired. The deadline now holds each joined signal once and removes it at stop. * fix(harness-sdk): bound the credential by the operation's deadline The token was asked before the deadline existed, so a slow refresh could hold a chat open past the discovery bound. The deadline now comes first and the credential races it. * fix(harness-sdk): give a call a deadline above the time it is waited on inline A remote tool is waited on for 15 s before the session backgrounds it, and the call's own default deadline was also 15 s, so a backgrounded call failed at that moment instead of answering. A call now defaults to 60 s; discovery keeps 15 s. * fix(mobile): publish the Kilo tools answer that a cache hit serves The registry builds a chat from the snapshot, not from the return value. A cache hit returned the ready answer while another scope's discovery had published connecting over it, so the chat opened on the base tools. * fix(mobile): keep each Kilo tools discovery on its caller's deadline A caller that joined a running discovery took the first caller's deadline, so an open that met a Retry waited fifteen seconds and a Retry that met an open was cut to four. A caller now joins only a discovery with its own deadline, and only the latest discovery is published. * fix(mobile): lock the Kilo tools switch when there is nothing to use In the unavailable state the switch read off while the chat's setting was on, so a tap wrote the value already stored and the switch snapped back. The view now says whether the switch can move, and the sheet disables it when not. * fix(deps): rebuild the lockfile on main's pins The merge rebuilt the lockfile from the branch side, so pnpm resolved again every package main had added since. That moved @peculiar/asn1-* to 2.9.5 and broke the passkey verification tests in apps/web. The lockfile is now main's, with only the harness SDK's own entries added. * fix(mobile): pass the segments to the toaster's tab bar check Main added a shouldHideTabBar call in app-root-providers.tsx with the old one-argument form. This branch reads the chat conversation from the segments, so the call now passes them too. * test(mobile): read the hoisted query client mock in the account switch test Main's lifecycle file already holds queryClientMock at module scope. The branch's test declared it again from a dynamic import, which lint refuses as a shadow and as an unbound method. * fix(mobile): describe the spend alerts notification setting Main added the spendAlerts notification category. The agent settings catalog maps every category to a description, so typecheck failed on the missing one. * fix(mobile): use the shared MCP servers copy in the chat sheet Main added profiles.mcp.title and profiles.mcp.add with the same English copy as modelChat.mcp.serversSection and modelChat.mcp.addServer, and the catalog check refuses two keys for one copy. The chat section now reads the profile keys, and the two chat keys are removed from every catalog. * test(mobile): load the real config module in the MCP URL test vitest.setup.ts now stubs @/lib/config for every pure suite, so the plain dynamic import in config.test.ts returned the stub and KILO_MCP_URL read as undefined. The test now loads the module through vi.importActual. * test(mobile): allow the remote MCP id slug in the case guard slugFor in remote-mcp-store.ts lowercases a server name to build the callable tool-name id. The id is never shown, so the file joins the non-display list. * feat(mobile): support configurable Quick Chat backends * test(harness): construct synthetic credential URL explicitly * fix(mobile): address Quick Chat backend review findings * fix(mobile): reopen a failed chat from Retry * test(mobile): allow protocol-identifier case normalization in backend files * fix(mobile): wait for stored backends before the send check * feat(mobile): add on-device Apple Foundation Models to Quick Chat * feat(mobile): add on-device Gemini Nano to Quick Chat on Android * fix(mobile): reference the Retry label in the on-device background copy * feat(mobile): add llama.rn GGUF models to Quick Chat * style(mobile): format the on-device GGUF files * test(mobile): allow the GGUF file-extension comparison in the case guard * fix(mobile): keep unreadable model entries from deleting downloads * test(mobile): split the model-index policy tests into their own suite * fix(mobile): never rewrite a model list holding an unreadable entry * fix(mobile): keep any list that did not parse in full from being rewritten * fix(mobile): read a GGUF result that carries nulls or only text * fix(mobile): treat a null parsed content as absent * test(mobile): pin the null-heavy llama.rn result at the client level * feat(mobile): send images to Quick Chat models that read them Quick Chat accepts images from the picker and the clipboard when the selected model reads images. The images stay on the device: the composer reads them as bounded JPEG base64 and hands them to the send path. The Cloud Agent upload path does not change. Capability contract: - backend-capabilities.ts: targetSupportsImages(targetId), resolvedTargetSupportsImages(target), rememberKiloImageModels(models) and useTargetSupportsImages(targetId). - LocalModelProvider.supportsImages(modelId). Every on-device provider returns false. - Kilo models read image support from the gateway catalog (architecture.input_modalities). Custom backend models get an images flag with a toggle. A stored model without the flag reads as text-only. The router replaces image parts with a short text marker for a target that does not read images, so a model switch after an image does not fail at the provider. The unanswered question keeps its images for Retry, and the transcript shows them. * feat(mobile): read images with downloaded vision models A downloaded GGUF model can now read images when it has a vision projector (mmproj). The catalog adds SmolVLM 500M Instruct with its projector, both pinned to one Hugging Face revision with size, SHA-256 and licence. - Downloads: a vision model downloads the model file, then the projector, as one download with one progress bar and one storage check. Cancel, failure and delete remove both files. - Records: a record has a vision flag. A record saved before this change reads as text-only. - Inspection: a vision model loads in full with its projector, and the download is accepted only when llama.cpp reports vision support. - Runtime: the context loads the projector after init with ctx_shift off. supportsImages is true only for a verified vision record. - Template: a user turn with an image goes to llama.rn as content parts with a base64 data URL. llama.rn decodes it, so no image file is written. A model without a projector does not get images. Direct-link models stay text-only. * fix(mobile): keep the catalog file type private * fix(mobile): show no upload progress on a local Quick Chat image A local image never uploads, so its chip starts with no progress value and the strip does not read "0%". * fix(mobile): release the native image on every encode path The encoder now releases the rendered image after a failed read or a failed save, not only after a successful one. Tests pin the release on each path, and that the images flag survives a backend save. * test(mobile): pin the native image release and the backend image flag Tests: the encoder releases each rendered image after a read, a resize, a failed save and a too-large result; a backend save keeps the image flag. * fix(mobile): a local Quick Chat image is not an upload A locally delivered image chip had status 'pending', which the strip treats as an upload in flight: a permanent spinner and an "Uploading…" announcement for an image that never leaves the device. Local chips now carry a 'local' status that starts no upload, shows no progress and announces the file size instead. * fix(mobile): drop the duplicate llama.rn proguard rule The merge of main added a second extraProguardRules next to main's combined rule, which app.config.ts rejects as a duplicate key. Keep main's combined rule. * ci(trufflehog): exclude the synthetic-credential test fixture security.test.ts builds a credential URL from the literal 'user:private-key@provider.example'. The commit that added it stays in this branch's history, so trufflehog reports it on every base..head scan even though a later commit replaced it with a constructed value. Exclude the fixture path. * fix(db): drop the stale duplicate 0263_drop_quick_chat migration main lands the drop as 0274_drop_quick_chat.sql (registered in the journal). The same drop sits in this branch's lineage as 0263_drop_quick_chat.sql, unregistered and colliding with 0263_spend_alerts_rollup_covering_index. The merge kept it because main never had that path. Remove it; the tree now matches main. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(harness-sdk): the kilo harness SDK #5878 * feat(mobile): rebuild the chat tab on the harness SDK #5895 * chore(kilo-mcp): refresh catalog.json from CI * feat(mobile): connect chat to the Kilo MCP server with the signed-in session (#6420) #6420 * chore: publish the verified app-agent settings tools + remote MCP stack as one PR (level 1) * fix(mobile): preserve settings and remote MCP state across cold loads * fix(mobile): satisfy async and control-flow lint rules * fix(harness-sdk): keep the library's abort link until the operation ends fetch resolves when the headers arrive, so the per-request unlink let close() leave an open stream running until the deadline timer fired. The deadline now holds each joined signal once and removes it at stop. * fix(harness-sdk): bound the credential by the operation's deadline The token was asked before the deadline existed, so a slow refresh could hold a chat open past the discovery bound. The deadline now comes first and the credential races it. * fix(harness-sdk): give a call a deadline above the time it is waited on inline A remote tool is waited on for 15 s before the session backgrounds it, and the call's own default deadline was also 15 s, so a backgrounded call failed at that moment instead of answering. A call now defaults to 60 s; discovery keeps 15 s. * fix(mobile): publish the Kilo tools answer that a cache hit serves The registry builds a chat from the snapshot, not from the return value. A cache hit returned the ready answer while another scope's discovery had published connecting over it, so the chat opened on the base tools. * fix(mobile): keep each Kilo tools discovery on its caller's deadline A caller that joined a running discovery took the first caller's deadline, so an open that met a Retry waited fifteen seconds and a Retry that met an open was cut to four. A caller now joins only a discovery with its own deadline, and only the latest discovery is published. * fix(mobile): lock the Kilo tools switch when there is nothing to use In the unavailable state the switch read off while the chat's setting was on, so a tap wrote the value already stored and the switch snapped back. The view now says whether the switch can move, and the sheet disables it when not. * fix(deps): rebuild the lockfile on main's pins The merge rebuilt the lockfile from the branch side, so pnpm resolved again every package main had added since. That moved @peculiar/asn1-* to 2.9.5 and broke the passkey verification tests in apps/web. The lockfile is now main's, with only the harness SDK's own entries added. * fix(mobile): pass the segments to the toaster's tab bar check Main added a shouldHideTabBar call in app-root-providers.tsx with the old one-argument form. This branch reads the chat conversation from the segments, so the call now passes them too. * test(mobile): read the hoisted query client mock in the account switch test Main's lifecycle file already holds queryClientMock at module scope. The branch's test declared it again from a dynamic import, which lint refuses as a shadow and as an unbound method. * fix(mobile): describe the spend alerts notification setting Main added the spendAlerts notification category. The agent settings catalog maps every category to a description, so typecheck failed on the missing one. * fix(mobile): use the shared MCP servers copy in the chat sheet Main added profiles.mcp.title and profiles.mcp.add with the same English copy as modelChat.mcp.serversSection and modelChat.mcp.addServer, and the catalog check refuses two keys for one copy. The chat section now reads the profile keys, and the two chat keys are removed from every catalog. * test(mobile): load the real config module in the MCP URL test vitest.setup.ts now stubs @/lib/config for every pure suite, so the plain dynamic import in config.test.ts returned the stub and KILO_MCP_URL read as undefined. The test now loads the module through vi.importActual. * test(mobile): allow the remote MCP id slug in the case guard slugFor in remote-mcp-store.ts lowercases a server name to build the callable tool-name id. The id is never shown, so the file joins the non-display list. * feat(mobile): support configurable Quick Chat backends * test(harness): construct synthetic credential URL explicitly * fix(mobile): address Quick Chat backend review findings * fix(mobile): reopen a failed chat from Retry * test(mobile): allow protocol-identifier case normalization in backend files * fix(mobile): wait for stored backends before the send check * feat(mobile): add on-device Apple Foundation Models to Quick Chat * feat(mobile): add on-device Gemini Nano to Quick Chat on Android * fix(mobile): reference the Retry label in the on-device background copy * feat(mobile): add llama.rn GGUF models to Quick Chat * style(mobile): format the on-device GGUF files * test(mobile): allow the GGUF file-extension comparison in the case guard * fix(mobile): keep unreadable model entries from deleting downloads * test(mobile): split the model-index policy tests into their own suite * fix(mobile): never rewrite a model list holding an unreadable entry * fix(mobile): keep any list that did not parse in full from being rewritten * fix(mobile): read a GGUF result that carries nulls or only text * fix(mobile): treat a null parsed content as absent * test(mobile): pin the null-heavy llama.rn result at the client level * feat(mobile): give the Apple on-device model the chat's tools Foundation Models calls harness tools through dynamic tools. The model's calls go to JavaScript as harness toolCall parts with the transcript ids. The generation waits while the harness runs them, and the next request resumes it with the results. Later requests rebuild the transcript with the tool calls and their outputs. JavaScript reduces each JSON Schema to what DynamicGenerationSchema can express. A tool that does not fit is left out and logged. Gemini Nano stays text-only because the ML Kit Prompt API has no function calling. * fix(mobile): keep native schema and message types module-private * fix(mobile): count tokens for a request that ends on tool outputs A resumed tool round sends a request whose last entry is a tool output. The Apple module refused to count it, so usage fell back to the estimate. Only generation now needs a user prompt last. * fix(mobile): drop the duplicate llama.rn proguard rule The merge of main added a second extraProguardRules next to main's combined rule, which app.config.ts rejects as a duplicate key. Keep main's combined rule. * fix(kilo-app): build the harness SDK before the EAS bundle The release EAS build bundles apps/mobile with Metro, which resolves @kilocode/harness-sdk through its package.json exports to dist/index.js. The workspace package is not built on EAS, so the Android and iOS bundles fail with 'none of these files exist' and the release never submits. Add the eas-build-post-install hook the EAS build runs after install. It builds the SDK, so Metro finds dist before it bundles. Verified with expo export:embed --eager --platform android: it fails without dist and succeeds after the hook builds it. * ci(trufflehog): exclude the synthetic-credential test fixture security.test.ts builds a credential URL from the literal 'user:private-key@provider.example'. The commit that added it stays in this branch's history, so trufflehog reports it on every base..head scan even though a later commit replaced it with a constructed value. Exclude the fixture path. * fix(db): drop the stale duplicate 0263_drop_quick_chat migration main lands the drop as 0274_drop_quick_chat.sql (registered in the journal). The same drop sits in this branch's lineage as 0263_drop_quick_chat.sql, unregistered and colliding with 0263_spend_alerts_rollup_covering_index. The merge kept it because main never had that path. Remove it; the tree now matches main. * fix(mobile): keep NativeImage module-private knip flags the export as unused: only native-request.ts builds an image part. Drop the export keyword. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
A new package,
@kilocode/harness-sdk. It holds a conversation with a model,keeps the model cache warm, stores the conversation, and summarises it when the
conversation outgrows the window.
One source runs on Node and on React Native. Nothing in
src/core/names aplatform:
fetch, SQLite and the source of random bytes are all plugin points.Read
packages/harness-sdk/README.mdfirst. It is what a consumer sees, andit is the fastest way to judge whether the shape is right.
AGENTS.mdis thesecond read: it holds why each decision was made and the measurement behind it.
Nothing on this branch imports the package, so it cannot change any behaviour
that runs today. #5895 is stacked on it and is the first consumer: the mobile
chat tab.
What it does
openSessiongives a handle.askstreams the answerback as events: text, thinking, tool calls, tool results, and a
donethatcarries the token counts and why the model stopped.
messages,responsesandchat_completions,from one internal model. A model the catalog knows nothing about is assumed to
speak all three, and the best one it actually speaks is used.
breakpoints do not move, which is what makes the provider's cache hit.
told a call is still running, carries on with what does not depend on it, and
the answer drives a round of its own when it lands. Four parties decide how
long it waits, in a fixed order.
questionToolasks the person;subagentToolhands atask to a session of its own and returns one answer.
node:sqliteor
expo-sqlite. A session can be continued, or cloned onto another model —which is how a person switches model mid-conversation without losing it.
The store plugins name no runtime type, so the package depends on neither.
keeps what it was told to keep.
How it is built
src/core/holds the contracts and the pure domain.src/plugins/holds everyimplementation, including the package's own defaults. A CI check asserts the
direction:
core/never importsplugins/.Eight plugin points, each a
Context.Tagwith a default this package owns:ModelClient,ToolRegistry,PromptAssembler,ModelCatalog,SessionStore,TokenSource,RetryPolicy,EntropySource.layerKilois the wiring almostevery caller writes; each part underneath is still swappable.
Nine entry points, so a consumer bundles only what they run. The conformance
checks and the shipped
fetchadapter each sit behind one of their own.Libraries do the work:
effectfor control flow, streams and resources,eventsource-parserfor SSE,typiafor validation, and the Anthropic andOpenAI SDKs as types only, so a wrong field name in a request body does not
compile. One exception is written down with its reason:
core/id.tsencodes aULID itself, because every
ulidbuild either dragsnode:cryptointo the coreor throws at import on a runtime with no global
crypto.typiaruns at compile time throughttsc, so no schema object exists at runtime and validation costs 0.005 us per event.
What proves it
pnpm test:e2e:allpnpm test:perfcheckStore,checkAssembler, shipped for plugin authorscheck:packageimports the built bundle from all 9 entry pointscheck:platformfails the build if it names a runtimecheck:migrationscatches the inlined SQL drifting from the schemaThe live runs are not in CI and must not be: they cost money and need a token.
They exist because a change to the wire or to the session has to be answered by
the provider, not by a fake.
Two matrices are recorded in
AGENTS.mdrather than described:history. A model whose provider caches at all reads above 0.99 of its input
from the cache.
payload the schema accepted. The subagent description was tuned against this
table until it did — two models had read the old text as naming the only two
uses, and answered that no tool of theirs could look a thing up.
Timing, measured rather than assumed: a 200 turn prompt assembles in under 20 us,
a whole request costs 48 us before the socket, and a streamed token costs 7 us
end to end against seconds of provider latency.
AGENTS.mdalso records oneoptimisation that was expected to be ten-fold and measured eleven percent, and
one memory measurement that was thrown away because it moved more than the thing
it was measuring.
Outside the package
.github/workflows/ci.yml— aharness-sdkjob, path-filtered, runningcheck:ci. The migration check is why it exists: nothing at run time noticeswhen the inlined SQL stops matching the schema.
.gitignore— the package'sdist/.pnpm-lock.yaml— the new dependencies.apps/web/AGENTS.md— ten linesnext devwrites into the file itself andre-adds when removed. Unrelated to this package; committed because that is
what the block asks for.
Not here