@aexol/spectral 0.9.231 → 0.9.240
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/README.md +47 -0
- package/dist/agent/index.d.ts +32 -5
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +209 -36
- package/dist/backend/generated-images.d.ts +1 -1
- package/dist/backend/generated-images.d.ts.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -4
- package/dist/commands/desktop.d.ts +167 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +540 -0
- package/dist/commands/serve.d.ts +9 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +90 -2
- package/dist/commands/update.d.ts +85 -5
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +288 -58
- package/dist/extensions/desktop-control/macos.d.ts.map +1 -1
- package/dist/extensions/desktop-control/macos.js +2 -2
- package/dist/extensions/logic-trace.d.ts +1 -1
- package/dist/extensions/logic-trace.d.ts.map +1 -1
- package/dist/extensions/logic-trace.js +199 -70
- package/dist/extensions/video-generation/index.d.ts +3 -0
- package/dist/extensions/video-generation/index.d.ts.map +1 -0
- package/dist/extensions/video-generation/index.js +299 -0
- package/dist/generated/zeus/const.d.ts.map +1 -1
- package/dist/generated/zeus/const.js +30 -0
- package/dist/generated/zeus/index.d.ts +140 -0
- package/dist/generated/zeus/index.d.ts.map +1 -1
- package/dist/generated/zeus/index.js +5 -0
- package/dist/mcp/server-manager.d.ts.map +1 -1
- package/dist/mcp/server-manager.js +6 -0
- package/dist/memory/tools/receive-agent-observations.d.ts.map +1 -1
- package/dist/memory/tools/receive-agent-observations.js +12 -2
- package/dist/relay/dispatcher.d.ts +13 -1
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +41 -2
- package/dist/sdk/agent-core/agent-loop.js +13 -3
- package/dist/sdk/agent-core/agent.d.ts +11 -4
- package/dist/sdk/agent-core/agent.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent.js +16 -4
- package/dist/sdk/agent-core/tool-call-registry.d.ts +75 -0
- package/dist/sdk/agent-core/tool-call-registry.d.ts.map +1 -0
- package/dist/sdk/agent-core/tool-call-registry.js +131 -0
- package/dist/sdk/agent-core/tool-execution.d.ts.map +1 -1
- package/dist/sdk/agent-core/tool-execution.js +103 -4
- package/dist/sdk/agent-core/types.d.ts +15 -4
- package/dist/sdk/agent-core/types.d.ts.map +1 -1
- package/dist/sdk/ai/index.d.ts +3 -0
- package/dist/sdk/ai/index.d.ts.map +1 -1
- package/dist/sdk/ai/index.js +3 -0
- package/dist/sdk/ai/providers/faux.d.ts.map +1 -1
- package/dist/sdk/ai/providers/faux.js +3 -0
- package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts +39 -6
- package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts.map +1 -1
- package/dist/sdk/ai/providers/leaked-tool-call-parser.js +159 -18
- package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.js +22 -8
- package/dist/sdk/ai/providers/openrouter-videos.d.ts +3 -0
- package/dist/sdk/ai/providers/openrouter-videos.d.ts.map +1 -0
- package/dist/sdk/ai/providers/openrouter-videos.js +412 -0
- package/dist/sdk/ai/providers/register-builtins.d.ts.map +1 -1
- package/dist/sdk/ai/providers/register-builtins.js +6 -0
- package/dist/sdk/ai/providers/simple-options.d.ts +1 -6
- package/dist/sdk/ai/providers/simple-options.d.ts.map +1 -1
- package/dist/sdk/ai/providers/simple-options.js +0 -22
- package/dist/sdk/ai/providers/transform-messages.d.ts.map +1 -1
- package/dist/sdk/ai/providers/transform-messages.js +52 -1
- package/dist/sdk/ai/types.d.ts +91 -10
- package/dist/sdk/ai/types.d.ts.map +1 -1
- package/dist/sdk/ai/video-models.d.ts +10 -0
- package/dist/sdk/ai/video-models.d.ts.map +1 -0
- package/dist/sdk/ai/video-models.generated.d.ts +440 -0
- package/dist/sdk/ai/video-models.generated.d.ts.map +1 -0
- package/dist/sdk/ai/video-models.generated.js +441 -0
- package/dist/sdk/ai/video-models.js +22 -0
- package/dist/sdk/ai/videos-api-registry.d.ts +14 -0
- package/dist/sdk/ai/videos-api-registry.d.ts.map +1 -0
- package/dist/sdk/ai/videos-api-registry.js +21 -0
- package/dist/sdk/ai/videos.d.ts +4 -0
- package/dist/sdk/ai/videos.d.ts.map +1 -0
- package/dist/sdk/ai/videos.js +13 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts +8 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +11 -0
- package/dist/sdk/coding-agent/core/extensions/loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/loader.js +14 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts +26 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +23 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts +2 -2
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +12 -2
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/messages.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/messages.js +42 -2
- package/dist/sdk/coding-agent/core/resource-loader.d.ts +11 -0
- package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/resource-loader.js +23 -6
- package/dist/sdk/coding-agent/core/sdk.d.ts +24 -2
- package/dist/sdk/coding-agent/core/sdk.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/sdk.js +15 -3
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +0 -8
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +3 -3
- package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.js +5 -0
- package/dist/sdk/coding-agent/index.d.ts +2 -2
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/index.js +1 -1
- package/dist/sdk-public.d.ts +96 -0
- package/dist/sdk-public.d.ts.map +1 -0
- package/dist/sdk-public.js +109 -0
- package/dist/server/agent-bridge/rehydrate.d.ts +2 -2
- package/dist/server/agent-bridge/rehydrate.d.ts.map +1 -1
- package/dist/server/agent-bridge/rehydrate.js +14 -0
- package/dist/server/agent-bridge/tps-metrics.d.ts +34 -0
- package/dist/server/agent-bridge/tps-metrics.d.ts.map +1 -1
- package/dist/server/agent-bridge/tps-metrics.js +22 -0
- package/dist/server/agent-bridge.d.ts +17 -2
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +108 -5
- package/dist/server/handlers/paths-list.d.ts +1 -1
- package/dist/server/handlers/paths-list.js +1 -1
- package/dist/server/inter-agent-broker.d.ts +12 -0
- package/dist/server/inter-agent-broker.d.ts.map +1 -1
- package/dist/server/inter-agent-broker.js +1 -0
- package/dist/server/preset-chain.d.ts +151 -0
- package/dist/server/preset-chain.d.ts.map +1 -0
- package/dist/server/preset-chain.js +250 -0
- package/dist/server/session-stream.d.ts +74 -1
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +238 -24
- package/dist/server/storage/rows.d.ts +2 -0
- package/dist/server/storage/rows.d.ts.map +1 -1
- package/dist/server/storage/schema.d.ts +2 -2
- package/dist/server/storage/schema.d.ts.map +1 -1
- package/dist/server/storage/schema.js +2 -1
- package/dist/server/storage.d.ts +7 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +142 -9
- package/dist/server/wire.d.ts +84 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/dist/utils/augment-path.d.ts +51 -0
- package/dist/utils/augment-path.d.ts.map +1 -0
- package/dist/utils/augment-path.js +234 -0
- package/dist/utils/open-browser.d.ts +55 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +163 -0
- package/dist/utils/serve-state.d.ts +264 -0
- package/dist/utils/serve-state.d.ts.map +1 -0
- package/dist/utils/serve-state.js +563 -0
- package/dist/utils/standalone.d.ts +14 -0
- package/dist/utils/standalone.d.ts.map +1 -0
- package/dist/utils/standalone.js +23 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -53,6 +53,11 @@ Requires Node.js **20 or newer**.
|
|
|
53
53
|
can create projects, open sessions, and chat with the agent on that machine
|
|
54
54
|
from any tab.
|
|
55
55
|
|
|
56
|
+
Steps 2 and 3 are one command: `spectral desktop`. It starts `serve` as a
|
|
57
|
+
background daemon (survives closing the terminal), waits until the relay is
|
|
58
|
+
genuinely connected, then opens the UI in your default browser — see
|
|
59
|
+
[`spectral desktop`](#spectral-desktop).
|
|
60
|
+
|
|
56
61
|
You can also run Spectral as a plain local TUI without the relay — just invoke
|
|
57
62
|
`spectral` (no subcommand) and it acts as a normal pi terminal session with the
|
|
58
63
|
Aexol MCP extension auto-loaded.
|
|
@@ -65,6 +70,7 @@ Aexol MCP extension auto-loaded.
|
|
|
65
70
|
| `spectral login` | Interactive auth. Verifies the key against the MCP backend and stores it. |
|
|
66
71
|
| `spectral logout` | Removes `~/.spectral/config.json`. Idempotent. |
|
|
67
72
|
| `spectral serve` | Connect this machine to the Aexol relay. Stays up; survives reconnects. |
|
|
73
|
+
| `spectral desktop` | Start `serve` as a daemon, wait for it, open the UI. `status` / `stop` too. |
|
|
68
74
|
| `spectral --version` | Print version. |
|
|
69
75
|
| `spectral --help` | Print Spectral header, then pi's full help. |
|
|
70
76
|
|
|
@@ -74,6 +80,39 @@ Aexol MCP extension auto-loaded.
|
|
|
74
80
|
| ----------------------- | ----------------------------------------------------- |
|
|
75
81
|
| `--machine-name <name>` | Override the display name (default: `os.hostname()`). |
|
|
76
82
|
|
|
83
|
+
### `spectral desktop`
|
|
84
|
+
|
|
85
|
+
The headless replacement for the removed Electron app: same lifecycle, no
|
|
86
|
+
bundled browser, and the UI is your own browser at
|
|
87
|
+
`https://aexol.ai/agent` (override with `SPECTRAL_LANDING_URL`).
|
|
88
|
+
|
|
89
|
+
| Command | Description |
|
|
90
|
+
| ----------------------- | ---------------------------------------------------------------------- |
|
|
91
|
+
| `spectral desktop` | Start the agent, or reuse the one already running, then open the UI. |
|
|
92
|
+
| `spectral desktop status` | Report pid / version / readiness signal. Exit `3` when not running. |
|
|
93
|
+
| `spectral desktop stop` | SIGTERM, then SIGKILL after 10s. Removes the pid / ready files. |
|
|
94
|
+
|
|
95
|
+
Mechanics worth knowing:
|
|
96
|
+
|
|
97
|
+
- Single instance via `~/.spectral/serve.pid`, claimed atomically (`O_EXCL`)
|
|
98
|
+
*before* the daemon is spawned — two simultaneous `spectral desktop` runs
|
|
99
|
+
start exactly one daemon. A dead pid is detected and pruned, so a crashed
|
|
100
|
+
daemon never blocks the next start, and a pid file held by a process this
|
|
101
|
+
command did not launch is never overwritten or deleted.
|
|
102
|
+
- If `~/.spectral/config.json` has no Studio token, `desktop` runs the
|
|
103
|
+
interactive `spectral login` flow first (and refuses to prompt when there is
|
|
104
|
+
no terminal, telling you to run `spectral login` yourself).
|
|
105
|
+
- Readiness is a real signal, not a timeout: the daemon prints
|
|
106
|
+
`SPECTRAL_SERVE_READY` once the relay WebSocket opens and writes
|
|
107
|
+
`serve-ready.json`. Opening the UI only after that avoids the
|
|
108
|
+
`machine_disconnected` flash you get if the page subscribes before the socket
|
|
109
|
+
exists.
|
|
110
|
+
- `~/.spectral/serve.log` is created `0600` and rotated to `serve.log.old` at
|
|
111
|
+
start once it passes 10 MB (`SPECTRAL_DESKTOP_LOG_MAX_BYTES`).
|
|
112
|
+
- The daemon is spawned detached with an augmented `PATH` (`/etc/paths`,
|
|
113
|
+
`getconf PATH`, Homebrew/nvm/Node dirs), so MCP servers declared as
|
|
114
|
+
`npx -y <pkg>` work even when Spectral was launched from a GUI context.
|
|
115
|
+
|
|
77
116
|
Anything that isn't a Spectral subcommand is forwarded verbatim to pi, so any pi
|
|
78
117
|
flag you know works. Example: `spectral -p "summarize this repo"`.
|
|
79
118
|
|
|
@@ -139,10 +178,18 @@ behaves like a vanilla pi install.
|
|
|
139
178
|
| `~/.spectral/config.json` | Aexol MCP URL + team API key. Created by `spectral login`. Mode `0600`. |
|
|
140
179
|
| `~/.spectral/machine.json` | Machine identity + relay JWT. Created on first `spectral serve`. |
|
|
141
180
|
| `~/.spectral/sessions.db` | Local SQLite for projects, sessions, messages. |
|
|
181
|
+
| `~/.spectral/serve.pid` | Relay daemon pid (single-instance lock). Pruned when stale. |
|
|
182
|
+
| `~/.spectral/serve-ready.json` | `{pid, ts, version}` written when the relay socket opens. |
|
|
183
|
+
| `~/.spectral/serve.log` | Daemon stdout/stderr, written by `spectral desktop`. |
|
|
142
184
|
| `SPECTRAL_CONFIG_DIR` | Override the directory above. |
|
|
143
185
|
| `SPECTRAL_MCP_URL` | Override the MCP URL at login time. |
|
|
144
186
|
| `SPECTRAL_BACKEND_URL` | Override the backend HTTP base for `spectral serve`. |
|
|
145
187
|
| `SPECTRAL_RELAY_URL` | Override the derived relay WebSocket URL. |
|
|
188
|
+
| `SPECTRAL_LANDING_URL` | UI that `spectral desktop` opens (default `https://aexol.ai/agent`). |
|
|
189
|
+
| `SPECTRAL_SERVE_LOG_PATH` | Daemon log path for `spectral desktop`. |
|
|
190
|
+
| `SPECTRAL_DESKTOP_READY_TIMEOUT_MS` | Readiness wait before `desktop` gives up (default 15000). |
|
|
191
|
+
| `SPECTRAL_DESKTOP_STOP_TIMEOUT_MS` | SIGTERM grace before SIGKILL in `desktop stop` (default 10000). |
|
|
192
|
+
| `SPECTRAL_DESKTOP_LOG_MAX_BYTES` | Serve-log size that triggers rotation at start (default 10485760). |
|
|
146
193
|
|
|
147
194
|
Pi's own auth state for the local TUI path (Codex subscription) lives in
|
|
148
195
|
`~/.pi/agent/auth.json` on the same machine. Spectral never reads it and never
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -24,14 +24,16 @@
|
|
|
24
24
|
* - Signal propagation (abort passthrough)
|
|
25
25
|
* - Access to observational memory via the `recall` and
|
|
26
26
|
* `write_project_observation` tools, which run against a parent-session
|
|
27
|
-
* point-in-time snapshot (branch + session id)
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* point-in-time snapshot (branch + session id). `receive_agent_observations`
|
|
28
|
+
* also runs inside subagents but polls with a DISTINCT derived
|
|
29
|
+
* observation session id (never the bare parent id); `share_project_observation`,
|
|
30
|
+
* `compact_context`, and `subagent` remain excluded
|
|
30
31
|
*/
|
|
31
|
-
import type { AgentTool } from "../sdk/agent-core/index.js";
|
|
32
|
+
import type { AgentEvent, AgentTool } from "../sdk/agent-core/index.js";
|
|
32
33
|
import { type ExtensionAPI, type ExtensionContext } from "../sdk/coding-agent/index.js";
|
|
33
34
|
import { type AgentConfig } from "./agents.js";
|
|
34
35
|
import type { ServerEvent } from "../server/wire.ts";
|
|
36
|
+
import { type TpsUsageFields } from "../server/agent-bridge/tps-metrics.ts";
|
|
35
37
|
import type { ReadonlySessionManager } from "../sdk/coding-agent/core/session-manager.js";
|
|
36
38
|
/**
|
|
37
39
|
* Build the system prompt for a subagent session.
|
|
@@ -49,7 +51,32 @@ export declare function buildSubagentSystemPrompt(baseSystemPrompt: string | und
|
|
|
49
51
|
* SessionManager. Each getBranch call returns fresh clones of the snapshot.
|
|
50
52
|
*/
|
|
51
53
|
export declare function createSubagentSessionManagerSnapshot(sessionManager: ExtensionContext["sessionManager"]): Pick<ReadonlySessionManager, "getBranch" | "getSessionId">;
|
|
52
|
-
export declare function resolveSubagentTools(cwd: string, ext: ExtensionAPI, ctx: ExtensionContext): AgentTool[];
|
|
54
|
+
export declare function resolveSubagentTools(cwd: string, ext: ExtensionAPI, ctx: ExtensionContext, agentName?: string): AgentTool[];
|
|
55
|
+
/**
|
|
56
|
+
* Runs a child agent with an independent per-subagent timeout.
|
|
57
|
+
*
|
|
58
|
+
* Combines the parent's abort signal with a fresh timeout AbortController so
|
|
59
|
+
* that no single subagent can exhaust the parent prompt's full timeout budget.
|
|
60
|
+
* The budget covers the WHOLE child run including any resume retries (see
|
|
61
|
+
* `runSingleAgent`), so wall time stays bounded by SUBAGENT_TIMEOUT_MS.
|
|
62
|
+
* On timeout, returns a clear error result instead of throwing so the parent
|
|
63
|
+
* agent can continue (e.g. stop a chain, or surface the error as the tool
|
|
64
|
+
* result for parallel/single modes).
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* LIVE TPS snapshot emitted by the subagent runner after each LLM round that
|
|
68
|
+
* produced tokens (subagent_progress contract). Carried through
|
|
69
|
+
* `onInternalEvent` alongside real `AgentEvent`s; the subagent extension maps
|
|
70
|
+
* it to the `subagent_tps` wire event for the bridge, which folds it into the
|
|
71
|
+
* throttled `subagent_progress` frames keyed by the parent tool call id.
|
|
72
|
+
*/
|
|
73
|
+
export interface SubagentTpsSnapshotEvent {
|
|
74
|
+
type: "subagent_tps_snapshot";
|
|
75
|
+
/** Token-weighted TPS fields so far (omitted fields = no sound data yet). */
|
|
76
|
+
tps: TpsUsageFields;
|
|
77
|
+
}
|
|
78
|
+
/** Internal events the subagent runner forwards through `onInternalEvent`. */
|
|
79
|
+
export type SubagentInternalEvent = AgentEvent | SubagentTpsSnapshotEvent;
|
|
53
80
|
/** Test hook: replace the run-level retry sleep (e.g. with an instant/collecting stub). */
|
|
54
81
|
export declare function setSubagentRetrySleepForTesting(fn?: (ms: number, signal?: AbortSignal) => Promise<void>): void;
|
|
55
82
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAEX,UAAU,EAEV,SAAS,EAET,MAAM,4BAA4B,CAAC;AAqBpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAKrB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACN,KAAK,WAAW,EAKhB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,uCAAuC,CAAC;AAS/C,OAAO,KAAK,EAAE,sBAAsB,EAAgB,MAAM,6CAA6C,CAAC;AAyGxG;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GACpF,MAAM,CAYR;AA2ND;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,GAChD,IAAI,CAAC,sBAAsB,EAAE,WAAW,GAAG,cAAc,CAAC,CAe5D;AAgCD,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,EACrB,SAAS,CAAC,EAAE,MAAM,GAChB,SAAS,EAAE,CAkDb;AAyBD;;;;;;;;;;GAUG;AACH;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,6EAA6E;IAC7E,GAAG,EAAE,cAAc,CAAC;CACpB;AAED,8EAA8E;AAC9E,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,wBAAwB,CAAC;AAmF1E,2FAA2F;AAC3F,wBAAgB,+BAA+B,CAC9C,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,IAAI,CAEN;AAmBD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC9D,MAAM,CAiBR;AAmmBD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,CAAC,EAAE;IACN,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,GACC,IAAI,CA+cN"}
|
package/dist/agent/index.js
CHANGED
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
* - Signal propagation (abort passthrough)
|
|
25
25
|
* - Access to observational memory via the `recall` and
|
|
26
26
|
* `write_project_observation` tools, which run against a parent-session
|
|
27
|
-
* point-in-time snapshot (branch + session id)
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* point-in-time snapshot (branch + session id). `receive_agent_observations`
|
|
28
|
+
* also runs inside subagents but polls with a DISTINCT derived
|
|
29
|
+
* observation session id (never the bare parent id); `share_project_observation`,
|
|
30
|
+
* `compact_context`, and `subagent` remain excluded
|
|
30
31
|
*/
|
|
31
32
|
import { agentLoop, agentLoopContinue, } from "../sdk/agent-core/index.js";
|
|
32
33
|
import { buildIsolatedRequestTimePrunedMessagesResult, COMPACT_CONTEXT_TOOL_NAME, } from "../memory/tools/compact-context.js";
|
|
@@ -37,6 +38,8 @@ import { sleep } from "../sdk/coding-agent/utils/sleep.js";
|
|
|
37
38
|
import { Type } from "typebox";
|
|
38
39
|
import { discoverAgents, agentMetadataBlock, } from "./agents.js";
|
|
39
40
|
import { loadAgentSettings } from "../server/handlers/agent-settings.js";
|
|
41
|
+
import { buildTpsUsageFields, computeTpsMetrics, } from "../server/agent-bridge/tps-metrics.js";
|
|
42
|
+
import { randomUUID } from "node:crypto";
|
|
40
43
|
// ---------------------------------------------------------------------------
|
|
41
44
|
// Built-in tool resolution (no spawn needed — tools are registered in-process)
|
|
42
45
|
// ---------------------------------------------------------------------------
|
|
@@ -53,6 +56,8 @@ import { compressSubagentToolResult } from "../memory/tool-output-compressor.js"
|
|
|
53
56
|
// ---------------------------------------------------------------------------
|
|
54
57
|
const MAX_PARALLEL_TASKS = 8;
|
|
55
58
|
const MAX_CONCURRENCY = 4;
|
|
59
|
+
/** Max steps in a chain call, mirroring the parallel batch cap. */
|
|
60
|
+
const MAX_CHAIN_STEPS = 8;
|
|
56
61
|
/**
|
|
57
62
|
* Per-subagent timeout. Each subagent invocation (single, parallel, chain)
|
|
58
63
|
* gets its own independent deadline so that no single subagent can
|
|
@@ -197,6 +202,40 @@ function truncateOutput(text, limit) {
|
|
|
197
202
|
}
|
|
198
203
|
return text;
|
|
199
204
|
}
|
|
205
|
+
/** Levenshtein edit distance between two strings (dependency-free). */
|
|
206
|
+
function levenshtein(a, b) {
|
|
207
|
+
const dp = [];
|
|
208
|
+
for (let i = 0; i <= a.length; i++)
|
|
209
|
+
dp[i] = [i];
|
|
210
|
+
for (let j = 0; j <= b.length; j++)
|
|
211
|
+
dp[0][j] = j;
|
|
212
|
+
for (let i = 1; i <= a.length; i++) {
|
|
213
|
+
for (let j = 1; j <= b.length; j++) {
|
|
214
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
215
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return dp[a.length][b.length];
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Suggests the closest known agent name for an unknown one.
|
|
222
|
+
* Case-insensitive prefix/substring match first, then Levenshtein ≤ 2.
|
|
223
|
+
*/
|
|
224
|
+
function suggestAgentName(unknownName, agents) {
|
|
225
|
+
const target = unknownName.toLowerCase();
|
|
226
|
+
let best;
|
|
227
|
+
for (const agent of agents) {
|
|
228
|
+
const name = agent.name.toLowerCase();
|
|
229
|
+
if (name.startsWith(target) || name.includes(target) || target.includes(name)) {
|
|
230
|
+
return agent.name;
|
|
231
|
+
}
|
|
232
|
+
const distance = levenshtein(target, name);
|
|
233
|
+
if (distance <= 2 && (!best || distance < best.distance)) {
|
|
234
|
+
best = { name: agent.name, distance };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return best?.name;
|
|
238
|
+
}
|
|
200
239
|
// ---------------------------------------------------------------------------
|
|
201
240
|
// Helpers — model resolution
|
|
202
241
|
// ---------------------------------------------------------------------------
|
|
@@ -258,14 +297,16 @@ export function createSubagentSessionManagerSnapshot(sessionManager) {
|
|
|
258
297
|
* - `compact_context`: depends on parent session/UI state (sessionManager,
|
|
259
298
|
* settingsManager, ui, compaction actions) that is unavailable inside the
|
|
260
299
|
* isolated `{ cwd, getSystemPrompt }` subagent context.
|
|
261
|
-
* - `
|
|
262
|
-
*
|
|
263
|
-
*
|
|
300
|
+
* - `share_project_observation`: requires the parent session identity to
|
|
301
|
+
* attribute shared observations; exposing it to subagents would let them
|
|
302
|
+
* misattribute messages and create share/receive feedback loops.
|
|
303
|
+
* - `receive_agent_observations` is NOT excluded: it runs with a derived
|
|
304
|
+
* `observationSessionId` (see `resolveSubagentTools`) so a subagent polls
|
|
305
|
+
* its own inbox instead of consuming the parent session's.
|
|
264
306
|
*/
|
|
265
307
|
const SUBAGENT_EXCLUDED_TOOL_NAMES = new Set([
|
|
266
308
|
"subagent",
|
|
267
309
|
COMPACT_CONTEXT_TOOL_NAME,
|
|
268
|
-
RECEIVE_AGENT_OBSERVATIONS_TOOL_NAME,
|
|
269
310
|
SHARE_PROJECT_OBSERVATION_TOOL_NAME,
|
|
270
311
|
]);
|
|
271
312
|
/**
|
|
@@ -275,8 +316,9 @@ const SUBAGENT_EXCLUDED_TOOL_NAMES = new Set([
|
|
|
275
316
|
const SUBAGENT_SESSION_SNAPSHOT_TOOL_NAMES = new Set([
|
|
276
317
|
RECALL_OBSERVATION_TOOL_NAME,
|
|
277
318
|
WRITE_PROJECT_OBSERVATION_TOOL_NAME,
|
|
319
|
+
RECEIVE_AGENT_OBSERVATIONS_TOOL_NAME,
|
|
278
320
|
]);
|
|
279
|
-
export function resolveSubagentTools(cwd, ext, ctx) {
|
|
321
|
+
export function resolveSubagentTools(cwd, ext, ctx, agentName) {
|
|
280
322
|
// Subagents receive every tool registered on the primary agent. Built-in
|
|
281
323
|
// tools are created fresh with the subagent's isolated cwd/jobs, and
|
|
282
324
|
// extension-registered tools are wrapped with a `SubagentToolContext`:
|
|
@@ -289,6 +331,16 @@ export function resolveSubagentTools(cwd, ext, ctx) {
|
|
|
289
331
|
// One shared snapshot per invocation so all snapshot-routed tools observe
|
|
290
332
|
// the same parent-session epoch (branch + session id).
|
|
291
333
|
const sharedSnapshot = createSubagentSessionManagerSnapshot(ctx.sessionManager);
|
|
334
|
+
// Distinct observation identity for inter-agent tools. Derived from the
|
|
335
|
+
// parent session id so the inbox stays project/session-scoped, but never
|
|
336
|
+
// equal to it: per-recipient delivery means a subagent polling with the
|
|
337
|
+
// bare parent id would consume (and mark delivered) the parent's
|
|
338
|
+
// broadcast observations. The suffix is per-invocation, so parallel and
|
|
339
|
+
// retried subagent runs each get their own inbox view.
|
|
340
|
+
const parentSessionId = ctx.sessionManager?.getSessionId?.();
|
|
341
|
+
const observationSessionId = parentSessionId
|
|
342
|
+
? `${parentSessionId}#sub:${(agentName ?? "agent").replace(/^@/, "")}:${randomUUID().slice(0, 8)}`
|
|
343
|
+
: undefined;
|
|
292
344
|
for (const toolInfo of ext.getAllTools()) {
|
|
293
345
|
const name = toolInfo.name;
|
|
294
346
|
if (SUBAGENT_EXCLUDED_TOOL_NAMES.has(name))
|
|
@@ -309,8 +361,8 @@ export function resolveSubagentTools(cwd, ext, ctx) {
|
|
|
309
361
|
if (!toolDef)
|
|
310
362
|
continue;
|
|
311
363
|
const isolatedContext = SUBAGENT_SESSION_SNAPSHOT_TOOL_NAMES.has(name)
|
|
312
|
-
? { cwd, sessionManager: sharedSnapshot, getSystemPrompt: ctx.getSystemPrompt }
|
|
313
|
-
: { cwd, getSystemPrompt: ctx.getSystemPrompt };
|
|
364
|
+
? { cwd, sessionManager: sharedSnapshot, getSystemPrompt: ctx.getSystemPrompt, observationSessionId }
|
|
365
|
+
: { cwd, getSystemPrompt: ctx.getSystemPrompt, observationSessionId };
|
|
314
366
|
tools.push(wrapToolDefinition(toolDef, () => isolatedContext));
|
|
315
367
|
}
|
|
316
368
|
return tools;
|
|
@@ -335,17 +387,6 @@ async function mapWithConcurrencyLimit(items, concurrency, fn) {
|
|
|
335
387
|
await Promise.all(workers);
|
|
336
388
|
return results;
|
|
337
389
|
}
|
|
338
|
-
/**
|
|
339
|
-
* Runs a child agent with an independent per-subagent timeout.
|
|
340
|
-
*
|
|
341
|
-
* Combines the parent's abort signal with a fresh timeout AbortController so
|
|
342
|
-
* that no single subagent can exhaust the parent prompt's full timeout budget.
|
|
343
|
-
* The budget covers the WHOLE child run including any resume retries (see
|
|
344
|
-
* `runSingleAgent`), so wall time stays bounded by SUBAGENT_TIMEOUT_MS.
|
|
345
|
-
* On timeout, returns a clear error result instead of throwing so the parent
|
|
346
|
-
* agent can continue (e.g. stop a chain, or surface the error as the tool
|
|
347
|
-
* result for parallel/single modes).
|
|
348
|
-
*/
|
|
349
390
|
async function runSingleAgentWithTimeout(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries) {
|
|
350
391
|
const timeoutMs = SUBAGENT_TIMEOUT_MS;
|
|
351
392
|
const timeoutSeconds = timeoutMs / 1000;
|
|
@@ -441,14 +482,15 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
441
482
|
const normalizedAgentName = agentName.replace(/^@/, "");
|
|
442
483
|
const agent = agents.find((a) => a.name === normalizedAgentName);
|
|
443
484
|
if (!agent) {
|
|
444
|
-
const available = agents.map((a) =>
|
|
485
|
+
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
486
|
+
const suggestion = suggestAgentName(normalizedAgentName, agents);
|
|
445
487
|
return {
|
|
446
488
|
agent: agentName,
|
|
447
489
|
agentSource: "unknown",
|
|
448
490
|
task,
|
|
449
491
|
exitCode: 1,
|
|
450
492
|
messages: [],
|
|
451
|
-
stderr: `Unknown agent: "${agentName}". Available agents: ${available}
|
|
493
|
+
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.${suggestion ? ` Did you mean "${suggestion}"?` : ""}`,
|
|
452
494
|
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
453
495
|
step,
|
|
454
496
|
};
|
|
@@ -508,7 +550,7 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
508
550
|
// per-agent tool filtering). Built-ins (read, bash, apply_patch) plus
|
|
509
551
|
// observational-memory tools (recall, read-project-observations) are
|
|
510
552
|
// always available so subagents can actually do work.
|
|
511
|
-
const tools = resolveSubagentTools(effectiveCwd, ext, ctx);
|
|
553
|
+
const tools = resolveSubagentTools(effectiveCwd, ext, ctx, agentName);
|
|
512
554
|
const currentResult = {
|
|
513
555
|
agent: agentName,
|
|
514
556
|
agentSource: agent.source,
|
|
@@ -534,6 +576,18 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
534
576
|
// Responses API reasoning metadata that lets the UI classify accumulated
|
|
535
577
|
// subagent thinking as activity (mirrors the main-session thinking metadata).
|
|
536
578
|
let thinkingMetadata;
|
|
579
|
+
// Per-invocation TPS accumulation (subagent_end contract). Declared before
|
|
580
|
+
// the try block so both the settled and thrown return paths can attach it;
|
|
581
|
+
// survives the run-level resume retry so retried rounds count like their
|
|
582
|
+
// usage does.
|
|
583
|
+
const tpsAccumulator = {
|
|
584
|
+
durationMsSum: 0,
|
|
585
|
+
outputTokens: 0,
|
|
586
|
+
outputDurationMs: 0,
|
|
587
|
+
ttftMsSum: 0,
|
|
588
|
+
ttftRounds: 0,
|
|
589
|
+
lastRoundTps: null,
|
|
590
|
+
};
|
|
537
591
|
const emitUpdate = () => {
|
|
538
592
|
if (onUpdate) {
|
|
539
593
|
const liveResult = {
|
|
@@ -616,6 +670,11 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
616
670
|
const collectedMessages = [];
|
|
617
671
|
let hadError = false;
|
|
618
672
|
let segments = 0;
|
|
673
|
+
// Per-LLM-round timing stamps (TPS contract), mirroring the bridge's
|
|
674
|
+
// main-session `pending` stamps: message_start opens the round, the
|
|
675
|
+
// first text/thinking delta ends the TTFT window, message_end closes it.
|
|
676
|
+
let roundStartAt;
|
|
677
|
+
let roundFirstDeltaAt;
|
|
619
678
|
// One child run may span several segments: the first one starts the
|
|
620
679
|
// loop, any later one resumes it. Tool results already recorded in
|
|
621
680
|
// `collectedMessages` are part of the resumed context, so tools are
|
|
@@ -627,15 +686,25 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
627
686
|
break;
|
|
628
687
|
}
|
|
629
688
|
onInternalEvent?.(event, parentToolCallId);
|
|
689
|
+
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
690
|
+
// New LLM round begins. Reset the delta stamp so a stale
|
|
691
|
+
// first-delta timestamp from a previous round cannot poison TTFT.
|
|
692
|
+
roundStartAt = Date.now();
|
|
693
|
+
roundFirstDeltaAt = undefined;
|
|
694
|
+
}
|
|
630
695
|
if (event.type === "message_update" && event.message?.role === "assistant") {
|
|
631
696
|
// Forward streaming text/thinking deltas so the UI can show the
|
|
632
697
|
// subagent process as it's generated, not just the final result.
|
|
633
698
|
const inner = event.assistantMessageEvent;
|
|
634
699
|
if (inner?.type === "text_delta" && inner.delta) {
|
|
700
|
+
if (roundFirstDeltaAt === undefined)
|
|
701
|
+
roundFirstDeltaAt = Date.now();
|
|
635
702
|
streamingText += inner.delta;
|
|
636
703
|
emitUpdate();
|
|
637
704
|
}
|
|
638
705
|
else if (inner?.type === "thinking_delta" && inner.delta) {
|
|
706
|
+
if (roundFirstDeltaAt === undefined)
|
|
707
|
+
roundFirstDeltaAt = Date.now();
|
|
639
708
|
thinkingText += inner.delta;
|
|
640
709
|
if (inner.metadata) {
|
|
641
710
|
thinkingMetadata = { ...(thinkingMetadata ?? {}), ...inner.metadata };
|
|
@@ -645,6 +714,9 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
645
714
|
continue;
|
|
646
715
|
}
|
|
647
716
|
if (event.type === "message_end" && event.message) {
|
|
717
|
+
// Stamp at handler entry so the round metrics below measure the
|
|
718
|
+
// whole stream (same source as the bridge's main-session path).
|
|
719
|
+
const roundEndAt = Date.now();
|
|
648
720
|
const msg = event.message;
|
|
649
721
|
collectedMessages.push(msg);
|
|
650
722
|
if (msg.role === "assistant") {
|
|
@@ -666,6 +738,36 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
666
738
|
currentResult.stopReason = msg.stopReason;
|
|
667
739
|
if (msg.errorMessage)
|
|
668
740
|
currentResult.errorMessage = msg.errorMessage;
|
|
741
|
+
// Per-round TPS metrics (mirrors the main-session token_usage
|
|
742
|
+
// stamping): message_start → first delta → message_end with this
|
|
743
|
+
// round's output tokens. Zero-token rounds contribute timing but
|
|
744
|
+
// are skipped in the TPS numerator/denominator.
|
|
745
|
+
const roundMetrics = computeTpsMetrics(roundStartAt, roundFirstDeltaAt, roundEndAt, usage?.output ?? 0);
|
|
746
|
+
if (roundMetrics.durationMs != null) {
|
|
747
|
+
tpsAccumulator.durationMsSum += roundMetrics.durationMs;
|
|
748
|
+
}
|
|
749
|
+
if (roundMetrics.tokensPerSecond != null &&
|
|
750
|
+
roundMetrics.durationMs != null) {
|
|
751
|
+
tpsAccumulator.outputTokens += usage?.output ?? 0;
|
|
752
|
+
tpsAccumulator.outputDurationMs += roundMetrics.durationMs;
|
|
753
|
+
tpsAccumulator.lastRoundTps = roundMetrics.tokensPerSecond;
|
|
754
|
+
}
|
|
755
|
+
if (roundMetrics.ttftMs != null) {
|
|
756
|
+
tpsAccumulator.ttftMsSum += roundMetrics.ttftMs;
|
|
757
|
+
tpsAccumulator.ttftRounds += 1;
|
|
758
|
+
}
|
|
759
|
+
// LIVE TPS snapshot (subagent_progress contract): after each LLM
|
|
760
|
+
// round that produced tokens, push the token-weighted
|
|
761
|
+
// accumulator snapshot through onInternalEvent so the bridge can
|
|
762
|
+
// fold it into the throttled subagent_progress frames keyed by
|
|
763
|
+
// the parent tool call id. Omitted until the first round with
|
|
764
|
+
// tokens; unsound fields are omitted by buildTpsUsageFields.
|
|
765
|
+
if (roundMetrics.tokensPerSecond != null) {
|
|
766
|
+
onInternalEvent?.({
|
|
767
|
+
type: "subagent_tps_snapshot",
|
|
768
|
+
tps: buildTpsUsageFields(tpsAccumulator),
|
|
769
|
+
}, parentToolCallId);
|
|
770
|
+
}
|
|
669
771
|
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
670
772
|
hadError = true;
|
|
671
773
|
}
|
|
@@ -693,6 +795,10 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
693
795
|
// shows the resumed segment's reasoning, not stale text.
|
|
694
796
|
thinkingText = "";
|
|
695
797
|
thinkingMetadata = undefined;
|
|
798
|
+
// Drop round stamps from the previous segment so a resumed segment's
|
|
799
|
+
// first assistant round measures from its own message_start onward.
|
|
800
|
+
roundStartAt = undefined;
|
|
801
|
+
roundFirstDeltaAt = undefined;
|
|
696
802
|
currentResult.stopReason = undefined;
|
|
697
803
|
currentResult.errorMessage = undefined;
|
|
698
804
|
currentResult.stderr = "";
|
|
@@ -773,6 +879,15 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
773
879
|
currentResult.errorMessage = `${currentResult.errorMessage} (failed after ${segments} attempts)`;
|
|
774
880
|
}
|
|
775
881
|
}
|
|
882
|
+
if (tpsAccumulator.durationMsSum > 0 || tpsAccumulator.ttftRounds > 0) {
|
|
883
|
+
currentResult.tps = {
|
|
884
|
+
...buildTpsUsageFields(tpsAccumulator),
|
|
885
|
+
outputTokens: tpsAccumulator.outputTokens,
|
|
886
|
+
outputDurationMs: tpsAccumulator.outputDurationMs,
|
|
887
|
+
ttftMsSum: tpsAccumulator.ttftMsSum,
|
|
888
|
+
ttftRounds: tpsAccumulator.ttftRounds,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
776
891
|
return currentResult;
|
|
777
892
|
}
|
|
778
893
|
catch (err) {
|
|
@@ -781,6 +896,15 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
781
896
|
currentResult.stopReason = "error";
|
|
782
897
|
currentResult.errorMessage = errorMsg;
|
|
783
898
|
currentResult.stderr = errorMsg;
|
|
899
|
+
if (tpsAccumulator.durationMsSum > 0 || tpsAccumulator.ttftRounds > 0) {
|
|
900
|
+
currentResult.tps = {
|
|
901
|
+
...buildTpsUsageFields(tpsAccumulator),
|
|
902
|
+
outputTokens: tpsAccumulator.outputTokens,
|
|
903
|
+
outputDurationMs: tpsAccumulator.outputDurationMs,
|
|
904
|
+
ttftMsSum: tpsAccumulator.ttftMsSum,
|
|
905
|
+
ttftRounds: tpsAccumulator.ttftRounds,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
784
908
|
return currentResult;
|
|
785
909
|
}
|
|
786
910
|
}
|
|
@@ -788,14 +912,20 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
788
912
|
// Tool parameter schemas
|
|
789
913
|
// ---------------------------------------------------------------------------
|
|
790
914
|
const TaskItem = Type.Object({
|
|
791
|
-
agent: Type.String({
|
|
792
|
-
|
|
915
|
+
agent: Type.String({
|
|
916
|
+
description: "Known agent name to invoke (use names from the available agents list)",
|
|
917
|
+
}),
|
|
918
|
+
task: Type.String({
|
|
919
|
+
description: "Full self-contained prompt text for the agent (plain string; subagents share no context with the caller)",
|
|
920
|
+
}),
|
|
793
921
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
794
922
|
});
|
|
795
923
|
const ChainItem = Type.Object({
|
|
796
|
-
agent: Type.String({
|
|
924
|
+
agent: Type.String({
|
|
925
|
+
description: "Known agent name to invoke (use names from the available agents list)",
|
|
926
|
+
}),
|
|
797
927
|
task: Type.String({
|
|
798
|
-
description: "
|
|
928
|
+
description: "Full self-contained prompt text with an optional {previous} placeholder, substituted with the previous step's output",
|
|
799
929
|
}),
|
|
800
930
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
801
931
|
});
|
|
@@ -804,13 +934,19 @@ const AgentScopeSchema = StringEnum(["user", "project", "both"], {
|
|
|
804
934
|
default: "both",
|
|
805
935
|
});
|
|
806
936
|
const SubagentParams = Type.Object({
|
|
807
|
-
agent: Type.Optional(Type.String({
|
|
808
|
-
|
|
937
|
+
agent: Type.Optional(Type.String({
|
|
938
|
+
description: "Known agent name to invoke (single mode; use names from the available agents list)",
|
|
939
|
+
})),
|
|
940
|
+
task: Type.Optional(Type.String({
|
|
941
|
+
description: "Full self-contained prompt text to delegate (single mode; plain string, subagents share no context with the caller)",
|
|
942
|
+
})),
|
|
809
943
|
tasks: Type.Optional(Type.Array(TaskItem, {
|
|
810
|
-
description: "
|
|
944
|
+
description: "Parallel batch of {agent, task} items for independent tasks (max 8, 4 run concurrently)",
|
|
945
|
+
minItems: 1,
|
|
811
946
|
})),
|
|
812
947
|
chain: Type.Optional(Type.Array(ChainItem, {
|
|
813
|
-
description: "
|
|
948
|
+
description: "Sequential steps of {agent, task}; {previous} substitutes the previous step's output (max 8 steps)",
|
|
949
|
+
minItems: 1,
|
|
814
950
|
})),
|
|
815
951
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
816
952
|
confirmProjectAgents: Type.Optional(Type.Boolean({
|
|
@@ -837,11 +973,19 @@ export default function subagentExtension(ext, opts) {
|
|
|
837
973
|
"Delegate tasks to specialized subagents with isolated context.",
|
|
838
974
|
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
|
839
975
|
'Default agent scope is "both" (user agents from ~/.spectral/agent/agents + project agents from .spectral/agents).',
|
|
840
|
-
'Set agentScope: "user" or "project" to limit scope.'
|
|
976
|
+
'Set agentScope: "user" or "project" to limit scope.',
|
|
977
|
+
"Exactly one mode per call.",
|
|
978
|
+
'Single (one task): {"agent":"researcher","task":"<full self-contained task text>"}',
|
|
979
|
+
'Parallel (independent tasks; max 8, 4 run concurrently): {"tasks":[{"agent":"researcher","task":"..."},{"agent":"executor","task":"..."}]}',
|
|
980
|
+
'Chain (dependent tasks; sequential): {"chain":[{"agent":"researcher","task":"..."},{"agent":"executor","task":"Implement {previous}"}]}',
|
|
981
|
+
"NEVER mix agent/task with tasks or chain in one call; never pass a single task via tasks (use single mode); task must be a plain string; use known agent names only.",
|
|
982
|
+
"Task texts must be fully self-contained prompts (subagents share no context with the caller).",
|
|
841
983
|
].join(" "),
|
|
842
984
|
promptSnippet: "Run a subagent to investigate, plan, review, or implement code changes",
|
|
843
985
|
promptGuidelines: [
|
|
844
986
|
"When the subagent tool is available, use it for non-trivial work where specialized delegation improves quality or speed, including bounded implementation, research, planning, review, and verification slices. Each subagent runs in an isolated context window.",
|
|
987
|
+
"Pick exactly one mode per call: one task → single {agent, task}; independent tasks → parallel {tasks: [...]} (max 8, 4 concurrent); dependent tasks → chain {chain: [...]} with {previous} substituting the prior step's output.",
|
|
988
|
+
"Task texts must be fully self-contained prompts (subagents share no context with the caller); never mix modes' fields (agent/task vs tasks/chain) in one call.",
|
|
845
989
|
],
|
|
846
990
|
parameters: SubagentParams,
|
|
847
991
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -943,6 +1087,17 @@ export default function subagentExtension(ext, opts) {
|
|
|
943
1087
|
finishedAt,
|
|
944
1088
|
});
|
|
945
1089
|
}
|
|
1090
|
+
else if (event.type === "subagent_tps_snapshot") {
|
|
1091
|
+
// LIVE TPS snapshot from the runner (subagent_progress
|
|
1092
|
+
// contract): forward as the internal `subagent_tps` wire
|
|
1093
|
+
// event; the bridge folds it into the throttled
|
|
1094
|
+
// subagent_progress frames for this tool call.
|
|
1095
|
+
emitSubagentEvent({
|
|
1096
|
+
type: "subagent_tps",
|
|
1097
|
+
toolCallId: parentToolCallId,
|
|
1098
|
+
...event.tps,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
946
1101
|
}
|
|
947
1102
|
: undefined;
|
|
948
1103
|
if (modeCount !== 1) {
|
|
@@ -951,7 +1106,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
951
1106
|
content: [
|
|
952
1107
|
{
|
|
953
1108
|
type: "text",
|
|
954
|
-
text: `Invalid parameters. Provide exactly one mode.\nAvailable agents: ${available}`,
|
|
1109
|
+
text: `Invalid parameters. Provide exactly one mode.\nAvailable agents: ${available}\n\nExamples:\nSingle: {"agent":"researcher","task":"<full self-contained task text>"}\nParallel: {"tasks":[{"agent":"researcher","task":"..."},{"agent":"executor","task":"..."}]}\nChain: {"chain":[{"agent":"researcher","task":"..."},{"agent":"executor","task":"Implement {previous}"}]}\nRetry with exactly one of: agent+task | tasks | chain`,
|
|
955
1110
|
},
|
|
956
1111
|
],
|
|
957
1112
|
details: makeDetails("single")([]),
|
|
@@ -990,6 +1145,16 @@ export default function subagentExtension(ext, opts) {
|
|
|
990
1145
|
}
|
|
991
1146
|
// ── Chain mode ──────────────────────────────────────────────
|
|
992
1147
|
if (params.chain && params.chain.length > 0) {
|
|
1148
|
+
if (params.chain.length > MAX_CHAIN_STEPS)
|
|
1149
|
+
return {
|
|
1150
|
+
content: [
|
|
1151
|
+
{
|
|
1152
|
+
type: "text",
|
|
1153
|
+
text: `Too many chain steps (${params.chain.length}). Max is ${MAX_CHAIN_STEPS}.`,
|
|
1154
|
+
},
|
|
1155
|
+
],
|
|
1156
|
+
details: makeDetails("chain")([]),
|
|
1157
|
+
};
|
|
993
1158
|
const results = [];
|
|
994
1159
|
let previousOutput = "";
|
|
995
1160
|
for (let i = 0; i < params.chain.length; i++) {
|
|
@@ -1101,8 +1266,15 @@ export default function subagentExtension(ext, opts) {
|
|
|
1101
1266
|
});
|
|
1102
1267
|
const successCount = results.filter((r) => r.exitCode === 0).length;
|
|
1103
1268
|
const summaries = results.map((r) => {
|
|
1104
|
-
const
|
|
1105
|
-
const
|
|
1269
|
+
const completed = r.exitCode === 0;
|
|
1270
|
+
const finalOutput = getFinalOutput(r.messages);
|
|
1271
|
+
// Failed tasks with no transcript surface their error message /
|
|
1272
|
+
// stderr instead of a bare "(no output)" so the caller can see
|
|
1273
|
+
// why the task failed (e.g. unknown agent names).
|
|
1274
|
+
const output = truncateOutput(completed || finalOutput.trim()
|
|
1275
|
+
? finalOutput
|
|
1276
|
+
: r.errorMessage || r.stderr, SUBAGENT_OUTPUT_LIMIT);
|
|
1277
|
+
const status = completed
|
|
1106
1278
|
? "completed"
|
|
1107
1279
|
: r.attempts && r.attempts > 1
|
|
1108
1280
|
? `failed (after ${r.attempts} attempts)`
|
|
@@ -1117,6 +1289,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
1117
1289
|
},
|
|
1118
1290
|
],
|
|
1119
1291
|
details: persistDetails(makeDetails("parallel")(results)),
|
|
1292
|
+
...(results.length > 0 && successCount === 0 ? { isError: true } : {}),
|
|
1120
1293
|
};
|
|
1121
1294
|
}
|
|
1122
1295
|
// ── Single mode ─────────────────────────────────────────────
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* @module @spectral/backend/generated-images
|
|
36
36
|
*/
|
|
37
|
-
export type GeneratedImageKind = "generated" | "attachment";
|
|
37
|
+
export type GeneratedImageKind = "generated" | "attachment" | "video";
|
|
38
38
|
export type UploadStep = "upload-url" | "put" | "confirm";
|
|
39
39
|
export interface UploadImageOptions {
|
|
40
40
|
/** Backend base url, e.g. `https://api.aexol.ai` (no trailing slash needed). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-images.d.ts","sourceRoot":"","sources":["../../src/backend/generated-images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"generated-images.d.ts","sourceRoot":"","sources":["../../src/backend/generated-images.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* - `spectral login` → interactive auth flow (writes ~/.spectral/config.json)
|
|
9
9
|
* - `spectral logout` → deletes ~/.spectral/config.json
|
|
10
10
|
* - `spectral serve` → start the Aexol relay WebSocket server (headless)
|
|
11
|
+
* - `spectral desktop`→ start `serve` detached + open the Studio UI in the
|
|
12
|
+
* browser (the thin replacement for the old desktop app)
|
|
11
13
|
* - `spectral bind` → link directory to an Aexol Studio project
|
|
12
14
|
* - `spectral unbind` → remove Aexol Studio project binding
|
|
13
15
|
* - `spectral --version` / `--help` → branded short-circuits, no auth needed.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG"}
|