@bastani/atomic 0.9.3-alpha.1 → 0.9.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/builtin/cursor/CHANGELOG.md +15 -0
- package/dist/builtin/cursor/README.md +2 -1
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/cursor/src/cursor-models-raw.json +2 -9
- package/dist/builtin/cursor/src/model-mapper.ts +14 -3
- package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +22 -0
- package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +53 -13
- package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +24 -7
- package/dist/builtin/cursor/src/proto/protobuf-codec.ts +3 -2
- package/dist/builtin/cursor/src/stream.ts +5 -11
- package/dist/builtin/cursor/src/transport-types.ts +3 -0
- package/dist/builtin/cursor/src/transport.ts +1 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +9 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -0
- package/dist/builtin/subagents/src/extension/index.ts +6 -3
- package/dist/builtin/subagents/src/extension/schemas.ts +0 -5
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -4
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +15 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +35 -1
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/dist/builtin/subagents/src/shared/types-async.ts +1 -0
- package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +27 -5
- package/dist/builtin/subagents/src/tui/render-layout.ts +27 -4
- package/dist/builtin/subagents/src/tui/render-result-animation.ts +22 -31
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +6 -6
- package/dist/builtin/subagents/src/tui/render-result.ts +20 -19
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +3 -3
- package/dist/builtin/subagents/src/tui/render-widget.ts +46 -7
- package/dist/builtin/subagents/src/tui/render.ts +2 -2
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +43 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +343 -0
- package/dist/builtin/workflows/src/durable/child-primitive.ts +79 -0
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +421 -0
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +171 -0
- package/dist/builtin/workflows/src/durable/factory.ts +96 -0
- package/dist/builtin/workflows/src/durable/file-backend.ts +433 -0
- package/dist/builtin/workflows/src/durable/index.ts +73 -0
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +217 -0
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +299 -0
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +171 -0
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +284 -0
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +180 -0
- package/dist/builtin/workflows/src/durable/types.ts +168 -0
- package/dist/builtin/workflows/src/durable/ui-primitive.ts +96 -0
- package/dist/builtin/workflows/src/engine/options.ts +3 -0
- package/dist/builtin/workflows/src/engine/primitives/parallel.ts +2 -2
- package/dist/builtin/workflows/src/engine/primitives/task.ts +4 -4
- package/dist/builtin/workflows/src/engine/primitives/ui.ts +22 -8
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +8 -0
- package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +69 -0
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +31 -0
- package/dist/builtin/workflows/src/engine/run.ts +148 -6
- package/dist/builtin/workflows/src/engine/runtime.ts +8 -2
- package/dist/builtin/workflows/src/extension/extension-factory.ts +6 -12
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +5 -1
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +48 -9
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +143 -4
- package/dist/builtin/workflows/src/runs/background/quit.ts +61 -0
- package/dist/builtin/workflows/src/runs/background/status.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +5 -5
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +74 -33
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +20 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +8 -7
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +19 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -10
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +5 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +25 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +16 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +20 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +23 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +30 -1
- package/dist/builtin/workflows/src/shared/store-public-types.ts +6 -2
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +12 -6
- package/dist/builtin/workflows/src/shared/types.ts +55 -0
- package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -1
- package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +41 -0
- package/dist/builtin/workflows/src/tui/graph-view-input.ts +82 -24
- package/dist/builtin/workflows/src/tui/graph-view-render.ts +7 -0
- package/dist/builtin/workflows/src/tui/graph-view-state.ts +22 -2
- package/dist/builtin/workflows/src/tui/graph-view-types.ts +4 -5
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -11
- package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +9 -3
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +11 -2
- package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +35 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +51 -17
- package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +36 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +5 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +3 -1
- package/dist/builtin/workflows/src/tui/status-list.ts +14 -2
- package/dist/builtin/workflows/src/tui/widget.ts +23 -8
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +5 -4
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +151 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +47 -30
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/messages.d.ts +1 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +46 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +12 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +15 -7
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +20 -9
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +2 -2
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +9 -3
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +2 -1
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/session-manager-list.d.ts +3 -3
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +27 -8
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +3 -1
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +55 -12
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/session-manager-tool-dependencies.d.ts +10 -0
- package/dist/core/session-manager-tool-dependencies.d.ts.map +1 -0
- package/dist/core/session-manager-tool-dependencies.js +133 -0
- package/dist/core/session-manager-tool-dependencies.js.map +1 -0
- package/dist/core/session-manager-types.d.ts +22 -0
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +1 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.js +12 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts +4 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js +131 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts +2 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +7 -1
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.js +15 -4
- package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +26 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/docs/compaction.md +2 -0
- package/docs/models.md +1 -1
- package/docs/providers.md +2 -1
- package/docs/session-format.md +6 -0
- package/docs/sessions.md +6 -0
- package/docs/workflows.md +105 -3
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.3-alpha.2] - 2026-06-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed custom tool renderer disposal to honor renderer-owned cleanup callbacks, preventing stale animation registry entries after terminal workflow tool rows are finalized ([#1518](https://github.com/bastani-inc/atomic/issues/1518)).
|
|
10
|
+
- Hardened session replay and LLM conversion so persisted context-compaction filters cannot leave orphaned `toolResult` messages after deleting their paired assistant `toolCall`, preventing GitHub Copilot Claude/Anthropic replay failures after repeated subagent runs ([#1527](https://github.com/bastani-inc/atomic/issues/1527)).
|
|
11
|
+
|
|
5
12
|
## [0.9.3-alpha.1] - 2026-06-25
|
|
6
13
|
|
|
7
14
|
### Breaking Changes
|
|
@@ -13,6 +20,7 @@
|
|
|
13
20
|
|
|
14
21
|
- Added one-time first-run onboarding that explains Atomic workflows, uses an onboarding editor placeholder, lets users opt into normal chat with `/chat`, preserves other slash commands, saves a pre-login pasted task in memory only, and hands the first ready ticket/spec/task to the normal coding-agent session with `goal`/`ralph` workflow-routing guidance.
|
|
15
22
|
- Added first-run onboarding routing guidance that raises the parent session to high reasoning when supported, asks the coding agent to first make a text-only scope estimate from tickets/GitHub issues/specs, routes directly when the task is clearly tiny or small with high confidence, and only uses targeted read-only `codebase-locator`/`codebase-analyzer`/`codebase-pattern-finder` probing when referenced context must be read or scope is medium, large, unclear, risky, or not obviously tiny before choosing `goal` or `ralph`.
|
|
23
|
+
- Excluded workflow-created (internal) sessions from the standard `/resume`, `atomic -r`, and `--continue` history by marking their `SessionHeader` with `internal: true` and optional `workflow: { runId, stageId, stageName }` linkage, while keeping them resumable via `/workflow resume`/`workflow({ action: "resume" })` and direct `--session <path>` access; added `includeInternal` opt-ins to `SessionManager.list`/`listAll`/`continueRecent`, a `markSessionInternal` method, robust full-line `readSessionHeader`, and regression tests ([#1504](https://github.com/bastani-inc/atomic/issues/1504)).
|
|
16
24
|
- Added a first-class `search` built-in and exposed `find`/`search` in normal coding sessions ([#1483](https://github.com/bastani-inc/atomic/issues/1483)).
|
|
17
25
|
- Added hashline snapshot anchors across `read`, `search`, `write`, and successful `edit` results, plus hashline line-range/block/multi-section edit scripts with stale-tag safety checks and snapshot-based recovery for non-overlapping file drift, empty-replace validation, and fresh post-mutation tags for follow-up edits ([#1483](https://github.com/bastani-inc/atomic/issues/1483)).
|
|
18
26
|
- Added disabled-by-default `bashInterceptor.enabled` settings support with built-in shell anti-pattern rules, a `/settings` **Bash Interceptor** toggle, and optional `user_bash` extension routing, without changing the default local-execution behavior ([#1483](https://github.com/bastani-inc/atomic/issues/1483)).
|
|
@@ -30,6 +38,7 @@
|
|
|
30
38
|
|
|
31
39
|
### Fixed
|
|
32
40
|
|
|
41
|
+
- Fixed extension loading in Windows package tests by lazy-initializing compiled-binary virtual modules, avoiding eager source-barrel resolution before Jiti aliases are installed.
|
|
33
42
|
- Fixed the first-run onboarding input placeholder so it uses muted TUI text and still renders a visible cursor while empty, making the startup composer read as an editable field instead of static copy.
|
|
34
43
|
- Fixed `@` file-reference autocomplete in the first-run onboarding editor before the asynchronous `fd` readiness check completes by falling back to the built-in synchronous path completer while preserving `@` prefixes and quoted paths.
|
|
35
44
|
- Fixed workflow config/discovery isolation so `ATOMIC_CODING_AGENT_DIR` prevents home-global workflows from shadowing the bundled first-run onboarding `goal` and `ralph` targets.
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.3-alpha.2] - 2026-06-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added scoped Cursor image-input support for known multimodal Claude, Composer, Gemini, GPT, and Kimi model families (`claude-`, `composer-`, `gemini-`, `gpt-`, `kimi-`), plus `cursor/grok-4.3`, including selected-image request serialization and mixed text/image MCP tool-result serialization.
|
|
10
|
+
- Added `cursor/grok-4.3` to the estimated fallback catalog.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Rejected empty or malformed base64 image payloads during Cursor selected-image and MCP image serialization with sanitized local errors, while accepting valid MIME-wrapped base64 with line whitespace.
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- Removed outdated Cursor Grok 4.20 entries from the estimated fallback catalog and no longer advertise Grok-family Cursor IDs as image-capable.
|
|
19
|
+
|
|
5
20
|
## [0.9.2] - 2026-06-23
|
|
6
21
|
|
|
7
22
|
### Changed
|
|
@@ -12,7 +12,8 @@ The unavoidable Atomic-specific integration difference is the provider surface:
|
|
|
12
12
|
|
|
13
13
|
## Limitations
|
|
14
14
|
|
|
15
|
-
-
|
|
15
|
+
- Image input is supported only for known multimodal Cursor Claude, Composer, Gemini, GPT, and Kimi model families (IDs beginning `claude-`, `composer-`, `gemini-`, `gpt-`, or `kimi-`), plus `grok-4.3`; text-only Cursor models still reject images.
|
|
16
|
+
- User images and mixed text/image MCP tool results are serialized for image-capable Cursor models. Image payloads must be non-empty standard base64; MIME-style line wrapping whitespace is accepted and stripped before serialization.
|
|
16
17
|
- Cursor's private API may change without notice.
|
|
17
18
|
- HTTP/2 transport requires the bundled `@bastani/atomic-natives` Rust/N-API native client for the current platform.
|
|
18
19
|
- Credentials are OAuth-only. Do not pass Cursor tokens via command-line args, environment variables, logs, or local proxy processes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/cursor",
|
|
3
|
-
"version": "0.9.3-alpha.
|
|
3
|
+
"version": "0.9.3-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Experimental first-party Atomic extension for Cursor OAuth, model discovery, and streaming provider registration.",
|
|
6
6
|
"contributors": [
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@bastani/atomic-natives": "0.9.3-alpha.
|
|
43
|
+
"@bastani/atomic-natives": "0.9.3-alpha.2",
|
|
44
44
|
"@bufbuild/protobuf": "^2.0.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -560,15 +560,8 @@
|
|
|
560
560
|
"maxTokens": 64000
|
|
561
561
|
},
|
|
562
562
|
{
|
|
563
|
-
"id": "grok-4
|
|
564
|
-
"name": "Grok 4.
|
|
565
|
-
"reasoning": false,
|
|
566
|
-
"contextWindow": 200000,
|
|
567
|
-
"maxTokens": 64000
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
"id": "grok-4-20-thinking",
|
|
571
|
-
"name": "Grok 4.20 Thinking",
|
|
563
|
+
"id": "grok-4.3",
|
|
564
|
+
"name": "Grok 4.3",
|
|
572
565
|
"reasoning": false,
|
|
573
566
|
"contextWindow": 200000,
|
|
574
567
|
"maxTokens": 64000
|
|
@@ -22,6 +22,8 @@ export interface CursorModelCatalog {
|
|
|
22
22
|
readonly models: readonly CursorUsableModel[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export type CursorModelInput = ["text"] | ["text", "image"];
|
|
26
|
+
|
|
25
27
|
export interface CursorProviderModelDefinition {
|
|
26
28
|
readonly id: string;
|
|
27
29
|
readonly name: string;
|
|
@@ -29,7 +31,7 @@ export interface CursorProviderModelDefinition {
|
|
|
29
31
|
readonly baseUrl?: string;
|
|
30
32
|
readonly reasoning: boolean;
|
|
31
33
|
readonly thinkingLevelMap?: ThinkingLevelMap;
|
|
32
|
-
readonly input:
|
|
34
|
+
readonly input: CursorModelInput;
|
|
33
35
|
readonly cost: { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number };
|
|
34
36
|
readonly contextWindow: number;
|
|
35
37
|
readonly maxTokens: number;
|
|
@@ -95,7 +97,7 @@ export function mapCursorCatalogToProviderModels(catalog: CursorModelCatalog): C
|
|
|
95
97
|
baseUrl: CURSOR_API_BASE_URL,
|
|
96
98
|
reasoning: supportsReasoning,
|
|
97
99
|
thinkingLevelMap: supportsEffort ? buildThinkingLevelMap(effortVariants, group.primaryId) : undefined,
|
|
98
|
-
input:
|
|
100
|
+
input: cursorModelInput(group.primaryId),
|
|
99
101
|
cost: subscriptionCost(),
|
|
100
102
|
contextWindow: chooseLargestNumber(group.variants.map((variant) => variant.contextWindow)) ?? ESTIMATED_CONTEXT_WINDOW,
|
|
101
103
|
maxTokens: chooseLargestNumber(group.variants.map((variant) => variant.maxTokens)) ?? ESTIMATED_MAX_TOKENS,
|
|
@@ -250,11 +252,20 @@ function chooseDisplayName(variants: readonly CursorVariant[], baseId: string, p
|
|
|
250
252
|
?? titleCaseModelId(baseId);
|
|
251
253
|
}
|
|
252
254
|
|
|
255
|
+
function cursorModelInput(id: string): CursorModelInput {
|
|
256
|
+
return supportsImageInputModelId(id) ? ["text", "image"] : ["text"];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function supportsImageInputModelId(id: string): boolean {
|
|
260
|
+
const variant = parseCursorVariant({ id });
|
|
261
|
+
return variant.baseId === "grok-4.3" || /^(claude|composer|gemini|gpt|kimi)(-|$)/iu.test(variant.baseId);
|
|
262
|
+
}
|
|
263
|
+
|
|
253
264
|
function supportsReasoningModelId(id: string): boolean {
|
|
254
265
|
const variant = parseCursorVariant({ id });
|
|
255
266
|
if (variant.effort || variant.thinking) return true;
|
|
256
267
|
if (variant.baseId === "default") return true;
|
|
257
|
-
return /^(claude|composer|gemini|gpt|
|
|
268
|
+
return /^(claude|composer|gemini|gpt|kimi)(-|$)/iu.test(variant.baseId);
|
|
258
269
|
}
|
|
259
270
|
|
|
260
271
|
function titleCaseModelId(id: string): string {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CursorImageBase64Context {
|
|
2
|
+
readonly kind: string;
|
|
3
|
+
readonly mimeType: string;
|
|
4
|
+
readonly index?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Cursor image protobuf serialization accepts canonical standard base64 after MIME line wrapping is removed.
|
|
8
|
+
const BASE64_WHITESPACE_PATTERN = /[\t\n\f\r ]+/gu;
|
|
9
|
+
const STANDARD_BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;
|
|
10
|
+
|
|
11
|
+
export function decodeStrictBase64ImageData(data: string, context: CursorImageBase64Context): Uint8Array {
|
|
12
|
+
const normalized = data.replace(BASE64_WHITESPACE_PATTERN, "");
|
|
13
|
+
if (normalized.length === 0 || normalized.length % 4 !== 0 || !STANDARD_BASE64_PATTERN.test(normalized)) {
|
|
14
|
+
throwInvalidBase64ImageData(context);
|
|
15
|
+
}
|
|
16
|
+
return Buffer.from(normalized, "base64");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function throwInvalidBase64ImageData(context: CursorImageBase64Context): never {
|
|
20
|
+
const index = context.index === undefined ? "" : ` at index ${context.index}`;
|
|
21
|
+
throw new Error(`Invalid ${context.kind} base64 image data${index} for MIME type ${context.mimeType}`);
|
|
22
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
3
|
+
import type { ImageContent } from "@earendil-works/pi-ai";
|
|
3
4
|
import { parseJsonObject, type JsonObject } from "../config.js";
|
|
4
|
-
import type { CursorRunRequest } from "../transport.js";
|
|
5
|
+
import type { CursorRunRequest, CursorToolResultContent } from "../transport.js";
|
|
5
6
|
import {
|
|
6
7
|
AgentClientMessageSchema,
|
|
7
8
|
AgentConversationTurnStructureSchema,
|
|
@@ -16,14 +17,17 @@ import {
|
|
|
16
17
|
McpToolDefinitionSchema,
|
|
17
18
|
ModelDetailsSchema,
|
|
18
19
|
SelectedContextSchema,
|
|
20
|
+
SelectedImageSchema,
|
|
19
21
|
ToolCallSchema,
|
|
20
22
|
UserMessageActionSchema,
|
|
21
23
|
UserMessageSchema,
|
|
22
24
|
type ConversationStateStructure,
|
|
23
25
|
type McpToolDefinition,
|
|
26
|
+
type SelectedImage,
|
|
24
27
|
type UserMessage,
|
|
25
28
|
} from "./agent_pb.js";
|
|
26
29
|
import { encodeMcpArgsMap, encodeProtobufValue, serializableJsonValue } from "./protobuf-codec-json.js";
|
|
30
|
+
import { decodeStrictBase64ImageData } from "./protobuf-codec-base64.js";
|
|
27
31
|
import { createMcpToolCallResult } from "./protobuf-codec-wire.js";
|
|
28
32
|
|
|
29
33
|
export interface ParsedAssistantTextStep {
|
|
@@ -36,13 +40,14 @@ export interface ParsedToolCallStep {
|
|
|
36
40
|
readonly toolCallId: string;
|
|
37
41
|
readonly toolName: string;
|
|
38
42
|
readonly arguments: JsonObject;
|
|
39
|
-
result?: { readonly content: string; readonly isError: boolean };
|
|
43
|
+
result?: { readonly content: string | readonly CursorToolResultContent[]; readonly isError: boolean; readonly fallbackText?: string };
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
export type ParsedTurnStep = ParsedAssistantTextStep | ParsedToolCallStep;
|
|
43
47
|
|
|
44
48
|
export interface ParsedTurn {
|
|
45
49
|
readonly userText: string;
|
|
50
|
+
readonly userImages: readonly ImageContent[];
|
|
46
51
|
readonly steps: ParsedTurnStep[];
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -69,6 +74,7 @@ export function buildCursorRequest(
|
|
|
69
74
|
conversationId: string,
|
|
70
75
|
checkpoint: Uint8Array | null,
|
|
71
76
|
existingBlobStore?: Map<string, Uint8Array>,
|
|
77
|
+
userImages: readonly ImageContent[] = [],
|
|
72
78
|
): { readonly requestBytes: Uint8Array; readonly blobStore: Map<string, Uint8Array> } {
|
|
73
79
|
const blobStore = new Map<string, Uint8Array>(existingBlobStore ?? []);
|
|
74
80
|
const systemBlobId = storeAsBlob(textEncoder.encode(JSON.stringify({ role: "system", content: systemPrompt })), blobStore);
|
|
@@ -76,7 +82,7 @@ export function buildCursorRequest(
|
|
|
76
82
|
const conversationState = checkpoint
|
|
77
83
|
? fromBinary(ConversationStateStructureSchema, checkpoint)
|
|
78
84
|
: buildConversationState(turns, blobStore, systemBlobId, selectedContextBlob);
|
|
79
|
-
const userMessage = createUserMessage(userText, selectedContextBlob);
|
|
85
|
+
const userMessage = createUserMessage(userText, selectedContextBlob, userImages);
|
|
80
86
|
const action = create(ConversationActionSchema, {
|
|
81
87
|
action: { case: "userMessageAction", value: create(UserMessageActionSchema, { userMessage }) },
|
|
82
88
|
});
|
|
@@ -96,7 +102,7 @@ function buildConversationState(
|
|
|
96
102
|
): ConversationStateStructure {
|
|
97
103
|
const turnBlobIds: Uint8Array[] = [];
|
|
98
104
|
for (const turn of turns) {
|
|
99
|
-
const userMessage = createUserMessage(turn.userText, selectedContextBlob);
|
|
105
|
+
const userMessage = createUserMessage(turn.userText, selectedContextBlob, turn.userImages);
|
|
100
106
|
const userMessageBlobId = storeAsBlob(toBinary(UserMessageSchema, userMessage), blobStore);
|
|
101
107
|
const stepBlobIds = turn.steps.map((step) => storeAsBlob(buildTurnStepBytes(step), blobStore));
|
|
102
108
|
const agentTurn = create(AgentConversationTurnStructureSchema, {
|
|
@@ -127,18 +133,39 @@ function buildConversationState(
|
|
|
127
133
|
});
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
function createUserMessage(text: string, selectedContextBlob: Uint8Array): UserMessage {
|
|
136
|
+
function createUserMessage(text: string, selectedContextBlob: Uint8Array, images: readonly ImageContent[] = []): UserMessage {
|
|
131
137
|
const messageId = randomUUID();
|
|
138
|
+
const selectedImages = images.map((image, index) => createSelectedImage(image, index));
|
|
132
139
|
return create(UserMessageSchema, {
|
|
133
140
|
text,
|
|
134
141
|
messageId,
|
|
135
|
-
selectedContext: create(SelectedContextSchema, {}),
|
|
142
|
+
selectedContext: create(SelectedContextSchema, { selectedImages }),
|
|
136
143
|
mode: 1,
|
|
137
144
|
selectedContextBlob,
|
|
138
145
|
correlationId: messageId,
|
|
139
146
|
});
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
function createSelectedImage(image: ImageContent, index: number): SelectedImage {
|
|
150
|
+
const data = decodeStrictBase64ImageData(image.data, { kind: "selected image", mimeType: image.mimeType, index });
|
|
151
|
+
const hash = createHash("sha256").update(data).digest("hex").slice(0, 16);
|
|
152
|
+
return create(SelectedImageSchema, {
|
|
153
|
+
uuid: randomUUID(),
|
|
154
|
+
path: `/atomic/inline-images/${hash}-${index}.${extensionForMimeType(image.mimeType)}`,
|
|
155
|
+
mimeType: image.mimeType,
|
|
156
|
+
dataOrBlobId: { case: "data", value: data },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function extensionForMimeType(mimeType: string): string {
|
|
161
|
+
const normalized = mimeType.toLowerCase();
|
|
162
|
+
if (normalized === "image/jpeg" || normalized === "image/jpg") return "jpg";
|
|
163
|
+
if (normalized === "image/png") return "png";
|
|
164
|
+
if (normalized === "image/gif") return "gif";
|
|
165
|
+
if (normalized === "image/webp") return "webp";
|
|
166
|
+
return "img";
|
|
167
|
+
}
|
|
168
|
+
|
|
142
169
|
function buildTurnStepBytes(step: ParsedTurnStep): Uint8Array {
|
|
143
170
|
if (step.kind === "assistantText") {
|
|
144
171
|
return toBinary(ConversationStepSchema, create(ConversationStepSchema, {
|
|
@@ -154,7 +181,7 @@ function buildTurnStepBytes(step: ParsedTurnStep): Uint8Array {
|
|
|
154
181
|
providerIdentifier: CURSOR_PROTO_CLIENT_NAME,
|
|
155
182
|
toolName,
|
|
156
183
|
}),
|
|
157
|
-
...(step.result ? { result: createMcpToolCallResult(step.result.content, step.result.isError) } : {}),
|
|
184
|
+
...(step.result ? { result: createMcpToolCallResult(step.result.content, step.result.isError, step.result.fallbackText ?? "") } : {}),
|
|
158
185
|
});
|
|
159
186
|
return toBinary(ConversationStepSchema, create(ConversationStepSchema, {
|
|
160
187
|
message: {
|
|
@@ -166,20 +193,22 @@ function buildTurnStepBytes(step: ParsedTurnStep): Uint8Array {
|
|
|
166
193
|
|
|
167
194
|
export function parseHistoricalTurns(messages: readonly CursorRunRequest["context"]["messages"][number][]): readonly ParsedTurn[] {
|
|
168
195
|
const turns: ParsedTurn[] = [];
|
|
169
|
-
let currentTurn: { userText: string; steps: ParsedTurnStep[]; toolCallById: Map<string, ParsedToolCallStep> } | undefined;
|
|
170
|
-
const ensureTurn = (): { userText: string; steps: ParsedTurnStep[]; toolCallById: Map<string, ParsedToolCallStep> } => {
|
|
171
|
-
currentTurn ??= { userText: "", steps: [], toolCallById: new Map() };
|
|
196
|
+
let currentTurn: { userText: string; userImages: readonly ImageContent[]; steps: ParsedTurnStep[]; toolCallById: Map<string, ParsedToolCallStep> } | undefined;
|
|
197
|
+
const ensureTurn = (): { userText: string; userImages: readonly ImageContent[]; steps: ParsedTurnStep[]; toolCallById: Map<string, ParsedToolCallStep> } => {
|
|
198
|
+
currentTurn ??= { userText: "", userImages: [], steps: [], toolCallById: new Map() };
|
|
172
199
|
return currentTurn;
|
|
173
200
|
};
|
|
174
201
|
const flushTurn = (): void => {
|
|
175
202
|
if (!currentTurn) return;
|
|
176
|
-
if (currentTurn.userText || currentTurn.
|
|
203
|
+
if (currentTurn.userText || currentTurn.userImages.length > 0 || currentTurn.steps.length > 0) {
|
|
204
|
+
turns.push({ userText: currentTurn.userText, userImages: currentTurn.userImages, steps: currentTurn.steps });
|
|
205
|
+
}
|
|
177
206
|
currentTurn = undefined;
|
|
178
207
|
};
|
|
179
208
|
for (const message of messages) {
|
|
180
209
|
if (message.role === "user") {
|
|
181
210
|
flushTurn();
|
|
182
|
-
currentTurn = { userText: textFromMessage(message), steps: [], toolCallById: new Map() };
|
|
211
|
+
currentTurn = { userText: textFromMessage(message), userImages: imagesFromUserMessage(message), steps: [], toolCallById: new Map() };
|
|
183
212
|
} else if (message.role === "assistant") {
|
|
184
213
|
const turn = ensureTurn();
|
|
185
214
|
for (const part of message.content) {
|
|
@@ -199,7 +228,7 @@ export function parseHistoricalTurns(messages: readonly CursorRunRequest["contex
|
|
|
199
228
|
turn.steps.push(step);
|
|
200
229
|
turn.toolCallById.set(step.toolCallId, step);
|
|
201
230
|
}
|
|
202
|
-
step.result = { content:
|
|
231
|
+
step.result = { content: message.content, isError: message.isError, fallbackText: rawToolResultText(message) };
|
|
203
232
|
}
|
|
204
233
|
}
|
|
205
234
|
flushTurn();
|
|
@@ -221,6 +250,17 @@ export function extractCurrentActionText(request: CursorRunRequest): string {
|
|
|
221
250
|
return last ? textFromMessage(last) : "";
|
|
222
251
|
}
|
|
223
252
|
|
|
253
|
+
export function extractCurrentActionImages(request: CursorRunRequest): readonly ImageContent[] {
|
|
254
|
+
const last = request.context.messages.at(-1);
|
|
255
|
+
if (!last || last.role !== "user") return [];
|
|
256
|
+
return imagesFromUserMessage(last);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function imagesFromUserMessage(message: Extract<CursorRunRequest["context"]["messages"][number], { readonly role: "user" }>): readonly ImageContent[] {
|
|
260
|
+
if (typeof message.content === "string") return [];
|
|
261
|
+
return message.content.filter((part): part is ImageContent => part.type === "image");
|
|
262
|
+
}
|
|
263
|
+
|
|
224
264
|
function rawToolResultText(message: Extract<CursorRunRequest["context"]["messages"][number], { readonly role: "toolResult" }>): string {
|
|
225
265
|
return message.content.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n");
|
|
226
266
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { create, toBinary } from "@bufbuild/protobuf";
|
|
3
|
-
import type { CursorControlMessage, CursorProtocolMessage, CursorServerMessage } from "../transport.js";
|
|
3
|
+
import type { CursorControlMessage, CursorProtocolMessage, CursorServerMessage, CursorToolResultContent } from "../transport.js";
|
|
4
4
|
import {
|
|
5
5
|
AgentClientMessageSchema,
|
|
6
6
|
BackgroundShellSpawnResultSchema,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
LsRejectedSchema,
|
|
18
18
|
LsResultSchema,
|
|
19
19
|
McpErrorSchema,
|
|
20
|
+
McpImageContentSchema,
|
|
20
21
|
McpResultSchema,
|
|
21
22
|
McpSuccessSchema,
|
|
22
23
|
McpTextContentSchema,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
type KvServerMessage,
|
|
41
42
|
type McpToolDefinition,
|
|
42
43
|
} from "./agent_pb.js";
|
|
44
|
+
import { decodeStrictBase64ImageData } from "./protobuf-codec-base64.js";
|
|
43
45
|
import { decodeMcpArgsMap } from "./protobuf-codec-json.js";
|
|
44
46
|
|
|
45
47
|
const NATIVE_EXEC_REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
|
|
@@ -203,29 +205,44 @@ export function encodeExecClientMessage(execNumericId: number | undefined, execI
|
|
|
203
205
|
return toBinary(AgentClientMessageSchema, clientMessage);
|
|
204
206
|
}
|
|
205
207
|
|
|
206
|
-
export function createMcpToolResult(
|
|
208
|
+
export function createMcpToolResult(content: string | readonly CursorToolResultContent[], isError: boolean, fallbackText = ""): ReturnType<typeof create<typeof McpResultSchema>> {
|
|
209
|
+
const text = typeof content === "string" ? content : fallbackText;
|
|
207
210
|
if (isError) {
|
|
208
211
|
return create(McpResultSchema, { result: { case: "error", value: create(McpErrorSchema, { error: text }) } });
|
|
209
212
|
}
|
|
210
213
|
return create(McpResultSchema, {
|
|
211
214
|
result: {
|
|
212
215
|
case: "success",
|
|
213
|
-
value: createMcpSuccess(
|
|
216
|
+
value: createMcpSuccess(content, fallbackText),
|
|
214
217
|
},
|
|
215
218
|
});
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
export function createMcpToolCallResult(
|
|
221
|
+
export function createMcpToolCallResult(content: string | readonly CursorToolResultContent[], isError: boolean, fallbackText = ""): ReturnType<typeof create<typeof McpToolResultSchema>> {
|
|
222
|
+
const text = typeof content === "string" ? content : fallbackText;
|
|
219
223
|
if (isError) {
|
|
220
224
|
return create(McpToolResultSchema, { result: { case: "error", value: create(McpToolErrorSchema, { error: text }) } });
|
|
221
225
|
}
|
|
222
|
-
return create(McpToolResultSchema, { result: { case: "success", value: createMcpSuccess(
|
|
226
|
+
return create(McpToolResultSchema, { result: { case: "success", value: createMcpSuccess(content, fallbackText) } });
|
|
223
227
|
}
|
|
224
228
|
|
|
225
|
-
function createMcpSuccess(
|
|
229
|
+
function createMcpSuccess(content: string | readonly CursorToolResultContent[], fallbackText: string): ReturnType<typeof create<typeof McpSuccessSchema>> {
|
|
230
|
+
const items = typeof content === "string"
|
|
231
|
+
? [createTextContentItem(content)]
|
|
232
|
+
: content.map((part, index) => part.type === "text" ? createTextContentItem(part.text) : createImageContentItem(part.data, part.mimeType, index));
|
|
226
233
|
return create(McpSuccessSchema, {
|
|
227
|
-
content:
|
|
234
|
+
content: items.length > 0 ? items : [createTextContentItem(fallbackText)],
|
|
228
235
|
isError: false,
|
|
229
236
|
});
|
|
230
237
|
}
|
|
231
238
|
|
|
239
|
+
function createTextContentItem(text: string): ReturnType<typeof create<typeof McpToolResultContentItemSchema>> {
|
|
240
|
+
return create(McpToolResultContentItemSchema, { content: { case: "text", value: create(McpTextContentSchema, { text }) } });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function createImageContentItem(data: string, mimeType: string, index: number): ReturnType<typeof create<typeof McpToolResultContentItemSchema>> {
|
|
244
|
+
return create(McpToolResultContentItemSchema, {
|
|
245
|
+
content: { case: "image", value: create(McpImageContentSchema, { data: decodeStrictBase64ImageData(data, { kind: "MCP image", mimeType, index }), mimeType }) },
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type McpToolDefinition,
|
|
16
16
|
type ModelDetails,
|
|
17
17
|
} from "./agent_pb.js";
|
|
18
|
-
import { blobKey, buildCursorRequest, buildMcpToolDefinitions, extractCurrentActionText, parseHistoricalTurns } from "./protobuf-codec-request.js";
|
|
18
|
+
import { blobKey, buildCursorRequest, buildMcpToolDefinitions, extractCurrentActionImages, extractCurrentActionText, parseHistoricalTurns } from "./protobuf-codec-request.js";
|
|
19
19
|
import { createMcpToolResult, decodeAgentServerMessage, encodeExecClientMessage, encodeKvClientMessage, encodeNativeExecRejection, encodeRequestContextResult } from "./protobuf-codec-wire.js";
|
|
20
20
|
|
|
21
21
|
// Cursor protocol codec intentionally follows the MIT-licensed
|
|
@@ -67,6 +67,7 @@ export class CursorProtobufProtocolCodec implements CursorProtocolCodec {
|
|
|
67
67
|
conversationIdValue,
|
|
68
68
|
storedState?.checkpoint ?? null,
|
|
69
69
|
storedState?.blobStore,
|
|
70
|
+
extractCurrentActionImages(request),
|
|
70
71
|
);
|
|
71
72
|
this.#blobStores.set(request.requestId, payload.blobStore);
|
|
72
73
|
this.#toolDefinitions.set(request.requestId, buildMcpToolDefinitions(request));
|
|
@@ -141,7 +142,7 @@ export class CursorProtobufProtocolCodec implements CursorProtocolCodec {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
encodeToolResult(result: CursorToolResultMessage): Uint8Array {
|
|
144
|
-
const mcpResult = createMcpToolResult(result.text, result.isError);
|
|
145
|
+
const mcpResult = createMcpToolResult(result.content ?? result.text, result.isError, result.text);
|
|
145
146
|
return encodeExecClientMessage(result.execNumericId, result.execId, "mcpResult", mcpResult);
|
|
146
147
|
}
|
|
147
148
|
|
|
@@ -88,8 +88,8 @@ export class CursorStreamAdapter {
|
|
|
88
88
|
if (!options?.apiKey) {
|
|
89
89
|
throw new Error("Cursor OAuth credentials are required. Run /login and select Cursor.");
|
|
90
90
|
}
|
|
91
|
-
if (hasImageInput(context)) {
|
|
92
|
-
throw new Error(
|
|
91
|
+
if (hasImageInput(context) && !model.input.includes("image")) {
|
|
92
|
+
throw new Error(`Cursor model ${model.id} does not support image input.`);
|
|
93
93
|
}
|
|
94
94
|
if (options.signal?.aborted) {
|
|
95
95
|
throw new CursorStreamAbortError();
|
|
@@ -216,16 +216,10 @@ export class CursorStreamAdapter {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
class CursorStreamAbortError extends Error {
|
|
219
|
-
constructor() {
|
|
220
|
-
super("Cursor stream aborted.");
|
|
221
|
-
this.name = "CursorStreamAbortError";
|
|
222
|
-
}
|
|
219
|
+
constructor() { super("Cursor stream aborted."); this.name = "CursorStreamAbortError"; }
|
|
223
220
|
}
|
|
224
221
|
class CursorStreamTimeoutError extends Error {
|
|
225
|
-
constructor() {
|
|
226
|
-
super("Cursor stream timed out while waiting for provider output.");
|
|
227
|
-
this.name = "CursorStreamTimeoutError";
|
|
228
|
-
}
|
|
222
|
+
constructor() { super("Cursor stream timed out while waiting for provider output."); this.name = "CursorStreamTimeoutError"; }
|
|
229
223
|
}
|
|
230
224
|
interface CursorPendingMessageRead {
|
|
231
225
|
readonly promise: Promise<IteratorResult<CursorServerMessage>>;
|
|
@@ -329,7 +323,7 @@ function getTrailingToolResults(context: Context): CursorToolResultMessage[] {
|
|
|
329
323
|
for (let index = context.messages.length - 1; index >= 0; index--) {
|
|
330
324
|
const message = context.messages[index];
|
|
331
325
|
if (!message || message.role !== "toolResult") break;
|
|
332
|
-
results.unshift({ toolCallId: message.toolCallId, toolName: message.toolName, text: textFromToolResult(message), isError: message.isError });
|
|
326
|
+
results.unshift({ toolCallId: message.toolCallId, toolName: message.toolName, text: textFromToolResult(message), content: message.content, isError: message.isError });
|
|
333
327
|
}
|
|
334
328
|
return results;
|
|
335
329
|
}
|
|
@@ -47,10 +47,13 @@ export type CursorControlMessage =
|
|
|
47
47
|
|
|
48
48
|
export type CursorProtocolMessage = CursorServerMessage | CursorControlMessage;
|
|
49
49
|
|
|
50
|
+
export type CursorToolResultContent = Extract<Context["messages"][number], { role: "toolResult" }>["content"][number];
|
|
51
|
+
|
|
50
52
|
export interface CursorToolResultMessage {
|
|
51
53
|
readonly toolCallId: string;
|
|
52
54
|
readonly toolName: string;
|
|
53
55
|
readonly text: string;
|
|
56
|
+
readonly content?: readonly CursorToolResultContent[];
|
|
54
57
|
readonly isError: boolean;
|
|
55
58
|
readonly execId?: string;
|
|
56
59
|
readonly execNumericId?: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.3-alpha.
|
|
3
|
+
"version": "0.9.3-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.3-alpha.
|
|
3
|
+
"version": "0.9.3-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.3-alpha.2] - 2026-06-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Prevented the async subagent status widget from briefly unmounting during reset-and-hydrate cycles when active background runs are still present, including Atomic host updates that deliver fresh UI context wrappers for the same logical session ([#1517](https://github.com/bastani-inc/atomic/issues/1517)).
|
|
10
|
+
- Fixed live subagent result animation cleanup to register a host-row disposer, so terminal workflow cleanup evicts animation registry entries instead of only clearing intervals ([#1518](https://github.com/bastani-inc/atomic/issues/1518)).
|
|
11
|
+
- Synced recent upstream subagent hardening so compact delegated tool-call summaries are preserved, fanout children keep their live nested subagent call/result history, duplicate concurrent subagent dispatches are rejected, provider-hostile chain schema conditionals are removed, and failed foreground runs include captured child output for diagnostics ([#1527](https://github.com/bastani-inc/atomic/issues/1527)).
|
|
12
|
+
- Eliminated the foreground subagent widget flicker that appeared once a running subagent panel grew tall enough to reach or exceed the terminal viewport. The live compact result no longer animates its spinner on an 80ms wall-clock timer; instead it shows an activity "pulse" glyph that advances exactly once per real progress update. Because the panel renders into chat scrollback, a timer-driven spinner cell that scrolled above pi-tui's viewport fold forced a destructive full-screen + scrollback clear on every tick — driving the indicator off genuine updates keeps line diffs tied to content that actually changed, so the differential renderer repaints only when progress does and never strobes.
|
|
13
|
+
|
|
5
14
|
## [0.9.3-alpha.1] - 2026-06-25
|
|
6
15
|
|
|
7
16
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.3-alpha.
|
|
3
|
+
"version": "0.9.3-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -10,7 +10,7 @@ import { discoverAgents } from "../agents/agents.ts";
|
|
|
10
10
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
11
11
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
12
12
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
13
|
-
import { renderLiveSubagentResult, renderSubagentResult, stopResultAnimations, stopWidgetAnimation, type SubagentResultRenderState } from "../tui/render.ts";
|
|
13
|
+
import { advanceResultPulseFrame, renderLiveSubagentResult, renderSubagentResult, stopResultAnimations, stopWidgetAnimation, type SubagentResultRenderState } from "../tui/render.ts";
|
|
14
14
|
import { SubagentParams } from "./schemas.ts";
|
|
15
15
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
16
16
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
@@ -69,7 +69,7 @@ type SubagentToolRenderState = SubagentResultRenderState;
|
|
|
69
69
|
function rebuildSlashResultContainer(
|
|
70
70
|
container: Container,
|
|
71
71
|
result: AgentToolResult<Details>,
|
|
72
|
-
options: { expanded: boolean; now?: number },
|
|
72
|
+
options: { expanded: boolean; now?: number; pulseFrame?: number },
|
|
73
73
|
theme: ExtensionContext["ui"]["theme"],
|
|
74
74
|
): void {
|
|
75
75
|
container.clear();
|
|
@@ -87,12 +87,14 @@ function createSlashResultComponent(
|
|
|
87
87
|
const container = new Container();
|
|
88
88
|
let lastVersion = -1;
|
|
89
89
|
let lastSnapshotNow = 0;
|
|
90
|
+
let pulseFrame = 0;
|
|
90
91
|
container.render = (width: number): string[] => {
|
|
91
92
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
92
93
|
if (snapshot.version !== lastVersion) {
|
|
93
94
|
lastVersion = snapshot.version;
|
|
94
95
|
lastSnapshotNow = Date.now();
|
|
95
|
-
|
|
96
|
+
pulseFrame = advanceResultPulseFrame(pulseFrame);
|
|
97
|
+
rebuildSlashResultContainer(container, snapshot.result, { ...options, now: lastSnapshotNow, pulseFrame }, theme);
|
|
96
98
|
}
|
|
97
99
|
return Container.prototype.render.call(container, width);
|
|
98
100
|
};
|
|
@@ -179,6 +181,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
179
181
|
baseCwd: "",
|
|
180
182
|
currentSessionId: null,
|
|
181
183
|
asyncJobs: new Map(),
|
|
184
|
+
subagentInProgress: false,
|
|
182
185
|
foregroundRuns: new Map(),
|
|
183
186
|
foregroundControls: new Map(),
|
|
184
187
|
lastForegroundControlId: null,
|
|
@@ -137,11 +137,6 @@ const ChainItem = Type.Object({
|
|
|
137
137
|
}, {
|
|
138
138
|
description: "Chain step: use {agent, task?, ...} for sequential, {parallel: [...]} for static concurrent execution, or {expand, parallel: {...}, collect} for dynamic fanout.",
|
|
139
139
|
additionalProperties: false,
|
|
140
|
-
allOf: [
|
|
141
|
-
{ if: { required: ["expand"] }, then: { required: ["parallel", "collect"], properties: { parallel: { type: "object" } } } },
|
|
142
|
-
{ if: { required: ["collect"] }, then: { required: ["expand", "parallel"], properties: { parallel: { type: "object" } } } },
|
|
143
|
-
{ not: { required: ["expand"], properties: { parallel: { type: "array", items: {} } } } },
|
|
144
|
-
],
|
|
145
140
|
});
|
|
146
141
|
|
|
147
142
|
const ControlOverrides = Type.Object({
|
|
@@ -395,10 +395,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
395
395
|
state.foregroundControls?.clear();
|
|
396
396
|
state.lastForegroundControlId = null;
|
|
397
397
|
state.resultFileCoalescer.clear();
|
|
398
|
-
if (ctx?.hasUI)
|
|
399
|
-
state.lastUiContext = ctx;
|
|
400
|
-
rerenderWidget(ctx, []);
|
|
401
|
-
}
|
|
398
|
+
if (ctx?.hasUI) state.lastUiContext = ctx;
|
|
402
399
|
};
|
|
403
400
|
|
|
404
401
|
return { ensurePoller, handleStarted, handleComplete, resetJobs, hydrateActiveJobs };
|