@jini-ai/daemon 0.6.15-rc.2 → 0.6.16
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 +12 -0
- package/README.md +14 -4
- package/dist/agent-executor/codex/home-config.js +3 -2
- package/dist/agent-executor/codex/home-staging.js +2 -2
- package/dist/agent-executor/codex/isolation.js +4 -3
- package/dist/agent-executor/contracts.d.ts +1 -1
- package/dist/agent-executor/contracts.js +0 -1
- package/dist/agent-executor/event-translation.d.ts +3 -3
- package/dist/agent-executor/event-translation.js +6 -6
- package/dist/agent-executor/events.js +65 -51
- package/dist/agent-executor/index.d.ts +5 -5
- package/dist/agent-executor/index.js +5 -5
- package/dist/agent-executor/launch.js +30 -46
- package/dist/agent-executor/live-session-state.js +2 -2
- package/dist/agent-executor/mcp-bridge.js +5 -0
- package/dist/agent-executor/message-relay.js +5 -6
- package/dist/agent-executor/run-output.js +7 -2
- package/dist/ai-trace-lifecycle.d.ts +1 -1
- package/dist/ai-trace-lifecycle.js +1 -1
- package/dist/close-status.js +6 -2
- package/dist/core/omit-keys.d.ts +7 -0
- package/dist/core/omit-keys.js +12 -0
- package/dist/core/read-only-tools.js +5 -1
- package/dist/core/tool-audit.js +11 -7
- package/dist/core/tool-gaps.js +1 -1
- package/dist/delegated-result-correlation.js +2 -2
- package/dist/frontend-capability-tools.d.ts +2 -0
- package/dist/frontend-capability-tools.js +7 -1
- package/dist/frontend-session-registry.d.ts +42 -3
- package/dist/frontend-session-registry.js +61 -9
- package/dist/http/agents.d.ts +4 -0
- package/dist/http/attachments.js +13 -12
- package/dist/http/component-catalog.js +1 -1
- package/dist/http/connectors.js +7 -5
- package/dist/http/db-ops.js +5 -0
- package/dist/http/delegated-tools.js +5 -0
- package/dist/http/frontend-control.d.ts +13 -0
- package/dist/http/frontend-control.js +14 -3
- package/dist/http/frontend-guest-scope.d.ts +32 -0
- package/dist/http/frontend-guest-scope.js +63 -0
- package/dist/http/frontend-sessions.d.ts +29 -6
- package/dist/http/frontend-sessions.js +108 -28
- package/dist/http/host-tools.js +7 -5
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/memory.js +3 -1
- package/dist/http/model-proxy.js +13 -8
- package/dist/http/research-tavily.adapter.js +2 -1
- package/dist/http/routines.js +7 -5
- package/dist/http/terminals.js +5 -0
- package/dist/http/tool-catalog.js +1 -1
- package/dist/http/xai.js +10 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/legacy-data-migration.d.ts +0 -12
- package/dist/legacy-data-migration.js +21 -13
- package/dist/remote-tool-executor.js +2 -1
- package/dist/routines/schedule.js +2 -1
- package/dist/routines/scheduler.js +6 -7
- package/dist/run-lifecycle.js +2 -1
- package/dist/store/agent-sessions/sql.js +7 -3
- package/dist/store/event-log/sqlite-kernel.js +7 -2
- package/dist/store/event-log/sqlite.js +6 -2
- package/dist/surface-exchanges.js +3 -3
- package/dist/terminal-session.d.ts +51 -20
- package/dist/terminal-session.js +10 -94
- package/dist/terminal.adapter.d.ts +22 -0
- package/dist/terminal.adapter.js +47 -0
- package/dist/tool-executor.d.ts +4 -2
- package/dist/tool-executor.js +95 -15
- package/dist/tool-recovery/recovery.js +5 -3
- package/dist/tool-recovery/redaction.js +1 -2
- package/dist/tool-recovery/stack.js +5 -1
- package/dist/tool-result-media.d.ts +4 -0
- package/dist/tool-result-media.js +10 -3
- package/dist/tool-result-surfaces.js +10 -9
- package/package.json +30 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 0.6.16
|
|
2
|
+
|
|
3
|
+
### Minor Changes
|
|
4
|
+
|
|
5
|
+
- Remove the GitHub Copilot CLI agent: its def, `createCopilotStreamHandler`/`CopilotStreamEvent`,
|
|
6
|
+
account model discovery and the `@github/copilot-sdk` optional peer, the MCP installer's `copilot`
|
|
7
|
+
slug, and its icon. A saved `copilot` agent id no longer resolves (`getAgentDef` returns `null`).
|
|
8
|
+
CopilotKit/AG-UI is unrelated and unchanged.
|
|
9
|
+
|
|
10
|
+
Drop peers no runtime entry loads: `express` from core and http-kit (type-only imports; type
|
|
11
|
+
consumers keep `@types/express`), `vue` from ui-kit, `react-dom` from vibecoding.
|
|
12
|
+
|
|
1
13
|
## 0.6.13
|
|
2
14
|
|
|
3
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -20,8 +20,18 @@ npm install @jini-ai/daemon
|
|
|
20
20
|
|
|
21
21
|
`node-pty` is an **optional peer dependency** — the package boots and every other capability
|
|
22
22
|
(run lifecycle, tool executor, agent executor, routines) works without it. Only
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
the native adapter at `@jini-ai/daemon/terminal` needs `node-pty` installed.
|
|
24
|
+
The default entry's session manager requires a host-supplied `terminalService` or `loadSpawnPty`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createTerminalSessionManager } from '@jini-ai/daemon';
|
|
28
|
+
import { loadRealSpawnPty } from '@jini-ai/daemon/terminal';
|
|
29
|
+
|
|
30
|
+
const manager = createTerminalSessionManager({}, { loadSpawnPty: () => loadRealSpawnPty({}) });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
With `@jini-ai/server`, pass that loader as `featureOptions.terminal.loadSpawnPty` to
|
|
34
|
+
`composeJiniKernel`, or as `loadTerminalSpawnPty` to `createLocalNodeDaemon`.
|
|
25
35
|
|
|
26
36
|
## What you get
|
|
27
37
|
|
|
@@ -47,8 +57,8 @@ spawn — needs `node-pty` installed; everything else has no native-addon depend
|
|
|
47
57
|
browser surface) and `createFrontendCapabilityRegistrations` (projects a frontend capability
|
|
48
58
|
manifest into `ToolRegistry` registrations — the only door into it).
|
|
49
59
|
- **Terminal sessions** — `createTerminalSessionManager`, `createTerminalToolRegistrations` — a
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
session manager built on `@jini-ai/platform`'s generic ring-buffer engine, plus session-token
|
|
61
|
+
gating. The optional native `loadRealSpawnPty` adapter lives at `@jini-ai/daemon/terminal`.
|
|
52
62
|
- **Routines** — `RoutineService`, `createInMemoryRoutineStore` (`routines/`) — a DST-safe
|
|
53
63
|
wall-clock scheduler plus CRUD + run-history store port.
|
|
54
64
|
- **Run-orchestration helpers** (`run/`) — `runResultFromStatus`, `deriveRunErrorCode`,
|
|
@@ -103,7 +103,7 @@ function scanBareTomlKeyChar(scan, ch) {
|
|
|
103
103
|
scan.current += ch;
|
|
104
104
|
}
|
|
105
105
|
// An inline or dotted root `mcp_servers` declaration, which the table scanner below cannot rewrite.
|
|
106
|
-
const INLINE_MCP_SERVERS = /^\s*(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*
|
|
106
|
+
const INLINE_MCP_SERVERS = /^\s*(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*[.=]/;
|
|
107
107
|
// `[<key>]` optionally followed only by a `# comment`; `[[...]]` never matches (see below).
|
|
108
108
|
const TOML_TABLE_HEADER = /^\s*\[([^[\]]+)\]\s*(#.*)?$/;
|
|
109
109
|
/**
|
|
@@ -203,7 +203,8 @@ function stripExistingJiniMcpServerTable(existingRaw, optional = {}) {
|
|
|
203
203
|
*/
|
|
204
204
|
export function buildCodexHomeConfigToml({ existingRaw, entry, inheritUserMcp = true, inheritUserSettings = true }, _optional = {}) {
|
|
205
205
|
const base = stripExistingJiniMcpServerTable(existingRaw ?? '', { allServers: !inheritUserMcp, mcpOnly: !inheritUserSettings });
|
|
206
|
-
const
|
|
206
|
+
const blockBreak = base.endsWith('\n') ? '\n' : '\n\n';
|
|
207
|
+
const separator = base.length === 0 ? '' : blockBreak;
|
|
207
208
|
return `${base}${separator}${buildCodexMcpServerToml({ entry: entry })}`;
|
|
208
209
|
}
|
|
209
210
|
//# sourceMappingURL=home-config.js.map
|
|
@@ -5,6 +5,7 @@ import { errorMessage } from '../values.js';
|
|
|
5
5
|
import { defaultReadMcpJsonFile, defaultWriteMcpJsonFile } from '../mcp-bridge.js';
|
|
6
6
|
import { buildCodexHomeConfigToml } from './home-config.js';
|
|
7
7
|
import { assertCodexHomeConfigParses, assertStagedHomeIsolated, CodexMcpIsolationError, codexIsolationRefusalMessage } from './isolation.js';
|
|
8
|
+
import { omitKeys } from '../../core/omit-keys.js';
|
|
8
9
|
/**
|
|
9
10
|
* Where `'codex-toml'` reads the operator's REAL Codex config from, to seed a run's scratch copy —
|
|
10
11
|
* never where it writes. Resolved against the daemon HOST process's own environment (`hostEnv`,
|
|
@@ -27,8 +28,7 @@ export function resolveSourceCodexHomeDir({ hostEnv }) {
|
|
|
27
28
|
* `mcp_skill_dependencies.rs`), and `codex exec` is `codex_exec` without it.
|
|
28
29
|
*/
|
|
29
30
|
export function withStagedCodexHome({ env, codexHomeDir }, _options = {}) {
|
|
30
|
-
|
|
31
|
-
return { ...rest, CODEX_HOME: codexHomeDir };
|
|
31
|
+
return { ...omitKeys(env, ['CODEX_INTERNAL_ORIGINATOR_OVERRIDE']), CODEX_HOME: codexHomeDir };
|
|
32
32
|
}
|
|
33
33
|
function defaultMkdtempCodexHome({ prefix }) {
|
|
34
34
|
return fsPromises.mkdtemp(join(tmpdir(), prefix));
|
|
@@ -15,7 +15,8 @@ export function codexIsolationRefusalMessage(reason) {
|
|
|
15
15
|
* known, and Codex could not load it either.
|
|
16
16
|
* @complexity O(n) in the file length.
|
|
17
17
|
*/
|
|
18
|
-
function parseCodexLayer(raw, path) {
|
|
18
|
+
function parseCodexLayer({ raw, path }, _optional = {}) {
|
|
19
|
+
// Codex isolation is a default security boundary; its parser must ship with the daemon.
|
|
19
20
|
try {
|
|
20
21
|
return parse(raw);
|
|
21
22
|
}
|
|
@@ -41,7 +42,7 @@ function assertTrustsNoProject(config, path) {
|
|
|
41
42
|
*/
|
|
42
43
|
export function assertCodexHomeConfigParses({ raw, path }) {
|
|
43
44
|
if (raw !== undefined)
|
|
44
|
-
parseCodexLayer(raw, path);
|
|
45
|
+
parseCodexLayer({ raw, path });
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
48
|
* The staged home config, parsed: it must declare no MCP server but the bridge and trust no
|
|
@@ -54,7 +55,7 @@ export function assertStagedHomeIsolated({ homeConfig, path }) {
|
|
|
54
55
|
const refusal = new CodexMcpIsolationError(`${path} declares MCP servers that can't be removed safely.`);
|
|
55
56
|
let config;
|
|
56
57
|
try {
|
|
57
|
-
config =
|
|
58
|
+
config = parseCodexLayer({ raw: homeConfig, path });
|
|
58
59
|
}
|
|
59
60
|
catch {
|
|
60
61
|
throw refusal;
|
|
@@ -23,7 +23,7 @@ export type StreamHandler = {
|
|
|
23
23
|
}): void;
|
|
24
24
|
flush(): void;
|
|
25
25
|
};
|
|
26
|
-
export declare const SUPPORTED_STREAM_FORMATS: readonly ["claude-stream-json", "json-event-stream", "
|
|
26
|
+
export declare const SUPPORTED_STREAM_FORMATS: readonly ["claude-stream-json", "json-event-stream", "qoder-stream-json", "acp-json-rpc", "pi-rpc", "plain"];
|
|
27
27
|
/** The families of the registry's stream-format this driver implements — see index.ts module doc. */
|
|
28
28
|
export type SupportedStreamFormat = (typeof SUPPORTED_STREAM_FORMATS)[number];
|
|
29
29
|
export type JsonStreamFormat = Exclude<SupportedStreamFormat, 'acp-json-rpc' | 'pi-rpc' | 'plain'>;
|
|
@@ -35,9 +35,9 @@ export declare function translateTurnEndEvent({ rawEvent }: {
|
|
|
35
35
|
* assertable in isolation.
|
|
36
36
|
*
|
|
37
37
|
* Defensive by construction: several real parser emissions carry fields
|
|
38
|
-
* looser than `RunAgentPayload`'s types promise (e.g.
|
|
39
|
-
*
|
|
40
|
-
* `
|
|
38
|
+
* looser than `RunAgentPayload`'s types promise (e.g. an untrusted
|
|
39
|
+
* tool event can carry a literal `null`, not the `string`
|
|
40
|
+
* `RunAgentPayload['tool_use']['id']` demands).
|
|
41
41
|
* Every field read here is defensively coerced (`asString`/
|
|
42
42
|
* `asOptionalString`/`asOptionalNumber`) rather than trusted, so a
|
|
43
43
|
* malformed or null field degrades to a safe default instead of
|
|
@@ -26,7 +26,7 @@ export function extractUsageTokens({ rawUsage }, _options = {}) {
|
|
|
26
26
|
* fields beyond the basic counts — `thought_tokens`,
|
|
27
27
|
* `cached_read_tokens`/`cached_write_tokens` (opencode/gemini/codex),
|
|
28
28
|
* `modelUsage`/`stopReason`/`isError` (qoder), a top-level `stopReason`
|
|
29
|
-
* (claude
|
|
29
|
+
* (claude). Cache/reasoning counters and stop reasons now survive tracing; vendor-only
|
|
30
30
|
* `modelUsage` and `isError` remain outside this neutral usage contract.
|
|
31
31
|
* @param rawEvent - The raw `{type:'usage', ...}` record from a stream parser.
|
|
32
32
|
* @returns The narrowed `RunAgentPayload` `usage` variant.
|
|
@@ -164,9 +164,9 @@ const EVENT_TYPE_TRANSLATORS = {
|
|
|
164
164
|
* assertable in isolation.
|
|
165
165
|
*
|
|
166
166
|
* Defensive by construction: several real parser emissions carry fields
|
|
167
|
-
* looser than `RunAgentPayload`'s types promise (e.g.
|
|
168
|
-
*
|
|
169
|
-
* `
|
|
167
|
+
* looser than `RunAgentPayload`'s types promise (e.g. an untrusted
|
|
168
|
+
* tool event can carry a literal `null`, not the `string`
|
|
169
|
+
* `RunAgentPayload['tool_use']['id']` demands).
|
|
170
170
|
* Every field read here is defensively coerced (`asString`/
|
|
171
171
|
* `asOptionalString`/`asOptionalNumber`) rather than trusted, so a
|
|
172
172
|
* malformed or null field degrades to a safe default instead of
|
|
@@ -227,9 +227,9 @@ export function applyAgentTranslationSideEffects({ payload, sessionId, sink }) {
|
|
|
227
227
|
sink.onSessionId({ sessionId });
|
|
228
228
|
if (payload.type === 'tool_use') {
|
|
229
229
|
sink.onToolCall({ toolUse: payload });
|
|
230
|
+
return;
|
|
230
231
|
}
|
|
231
|
-
|
|
232
|
+
if ((payload.type === 'text_delta' || payload.type === 'thinking_delta') && payload.delta.length > 0)
|
|
232
233
|
sink.onUserVisibleOutput();
|
|
233
|
-
}
|
|
234
234
|
}
|
|
235
235
|
//# sourceMappingURL=event-translation.js.map
|
|
@@ -7,8 +7,8 @@ import { createModelReceiptTracker } from './model-receipts.js';
|
|
|
7
7
|
import { observeRunOutput } from './run-output.js';
|
|
8
8
|
import { messageContentWithImages } from '../attachment-content.js';
|
|
9
9
|
import { redactSecrets, } from '@jini-ai/core';
|
|
10
|
-
import { createClaudeStreamHandler,
|
|
11
|
-
import {
|
|
10
|
+
import { createClaudeStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, attachAcpSession, attachPiRpcSession, } from '@jini-ai/agent-runtime';
|
|
11
|
+
import { listProcessSnapshots, stopProcesses, } from '@jini-ai/platform/process';
|
|
12
12
|
import { classifyRunCloseStatus, } from '../close-status.js';
|
|
13
13
|
import { resolveContinuationTransport, } from '../continuation/continuation-transport.js';
|
|
14
14
|
import { resultContent, } from '../delegated-tool-bridge.js';
|
|
@@ -42,12 +42,15 @@ function createStreamHandlerForDef(def, streamFormat, onEvent) {
|
|
|
42
42
|
switch (streamFormat) {
|
|
43
43
|
case 'claude-stream-json':
|
|
44
44
|
return createClaudeStreamHandler({ onEvent: onEvent }, { costBasis: def.usageCostBasis ?? 'run' });
|
|
45
|
-
case 'copilot-stream-json':
|
|
46
|
-
return createCopilotStreamHandler({ onEvent: onEvent });
|
|
47
45
|
case 'qoder-stream-json':
|
|
48
46
|
return createQoderStreamHandler({ onEvent: onEvent });
|
|
49
47
|
case 'json-event-stream':
|
|
50
48
|
return createJsonEventStreamHandler({ kind: def.eventParser ?? '', onEvent: onEvent });
|
|
49
|
+
default: {
|
|
50
|
+
// Unreachable: every stream format has a case above, and a new one fails typecheck here.
|
|
51
|
+
const unknownFormat = streamFormat;
|
|
52
|
+
throw new TypeError(`Unknown agent stream format: ${String(unknownFormat)}`);
|
|
53
|
+
}
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
@@ -463,9 +466,8 @@ export function wireChildLifecycle(ctx) {
|
|
|
463
466
|
*/
|
|
464
467
|
function handleTurnEnd(stopReason) {
|
|
465
468
|
const toolUse = pendingToolUse;
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
continuation !== undefined &&
|
|
469
|
+
const toolTurnPending = stopReason === 'tool_use' && toolUse !== undefined && continuation !== undefined;
|
|
470
|
+
const shouldInject = toolTurnPending &&
|
|
469
471
|
resolveContinuationTransport({ def: def }) === 'stdin-injection' &&
|
|
470
472
|
continuation.autonomousToolNames.has(toolUse.name);
|
|
471
473
|
if (!shouldInject) {
|
|
@@ -547,10 +549,10 @@ export function wireChildLifecycle(ctx) {
|
|
|
547
549
|
return lifecycle.emit({ runId, input: { event: 'agent', data: statusWithChildIdentity({ payload, childPid: ctx.child.pid }, {}) } });
|
|
548
550
|
});
|
|
549
551
|
}
|
|
550
|
-
|
|
552
|
+
if (translation.kind === 'error') {
|
|
551
553
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
|
|
552
554
|
}
|
|
553
|
-
|
|
555
|
+
if (translation.kind === 'turn-end') {
|
|
554
556
|
handleTurnEnd(translation.stopReason);
|
|
555
557
|
}
|
|
556
558
|
});
|
|
@@ -607,7 +609,8 @@ export function wireChildLifecycle(ctx) {
|
|
|
607
609
|
}
|
|
608
610
|
/**
|
|
609
611
|
* Emits the accumulated `'until-close'` stdout — sanitized — as exactly one raw `'stdout'` echo
|
|
610
|
-
* plus one `text_delta
|
|
612
|
+
* plus one `text_delta` for plain output, or through the declared structured parser. Both use
|
|
613
|
+
* the same `enqueueEmit` FIFO queue every other emit uses, so the
|
|
611
614
|
* flush is ordered after every already-queued event and before `finish()`'s `'end'`. A no-op for
|
|
612
615
|
* every live-streaming def (nothing was ever accumulated) and for a buffered run that produced
|
|
613
616
|
* no stdout at all — an empty `text_delta` is noise, not information.
|
|
@@ -622,10 +625,19 @@ export function wireChildLifecycle(ctx) {
|
|
|
622
625
|
const safe = sanitizeBufferedStdout ? sanitizeBufferedStdout({ fullText: bufferedStdout }) : bufferedStdout;
|
|
623
626
|
bufferedStdout = '';
|
|
624
627
|
bufferedStdoutBytes = 0;
|
|
625
|
-
const
|
|
626
|
-
? `${safe}${bufferedStdoutTruncationNotice(droppedStdoutBytes, ctx.bufferedStdoutMaxBytes)}`
|
|
627
|
-
: safe;
|
|
628
|
+
const dropped = droppedStdoutBytes;
|
|
628
629
|
droppedStdoutBytes = 0;
|
|
630
|
+
if (streamHandler !== null) {
|
|
631
|
+
// Redact the complete wire text before parsing: structured errors can contain sign-in URLs too.
|
|
632
|
+
stdoutDiagnostics.feed({ text: safe });
|
|
633
|
+
streamHandler.feed({ chunk: safe });
|
|
634
|
+
if (dropped > 0)
|
|
635
|
+
enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'error', data: {
|
|
636
|
+
message: bufferedStdoutTruncationNotice(dropped, ctx.bufferedStdoutMaxBytes),
|
|
637
|
+
} } }));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const text = dropped > 0 ? `${safe}${bufferedStdoutTruncationNotice(dropped, ctx.bufferedStdoutMaxBytes)}` : safe;
|
|
629
641
|
if (text.length === 0)
|
|
630
642
|
return;
|
|
631
643
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
|
|
@@ -634,27 +646,27 @@ export function wireChildLifecycle(ctx) {
|
|
|
634
646
|
child.stdout?.on('data', (chunk) => {
|
|
635
647
|
const text = chunk.toString('utf8');
|
|
636
648
|
stdoutDiagnostics.record({ chunk, text });
|
|
637
|
-
if (streamFormat === 'plain')
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
droppedStdoutBytes += chunkBytes;
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
bufferedStdout += text;
|
|
655
|
-
bufferedStdoutBytes += chunkBytes;
|
|
649
|
+
if (streamFormat === 'plain' && text.length > 0)
|
|
650
|
+
userVisibleOutputSeen = true;
|
|
651
|
+
if (bufferStdoutUntilClose) {
|
|
652
|
+
// Nothing is emitted on *either* channel yet — see this function's doc on why holding the
|
|
653
|
+
// raw echo back matters as much as holding back the chat copy.
|
|
654
|
+
//
|
|
655
|
+
// Whole chunks only: a chunk that would cross the ceiling is dropped entirely rather than
|
|
656
|
+
// sliced to fit, which keeps the accumulator free of half-written multi-byte characters (a
|
|
657
|
+
// `data` event boundary already need not align with one) and makes the kept prefix exactly
|
|
658
|
+
// the bytes some prefix of chunks produced. Everything after the first refusal is dropped
|
|
659
|
+
// too — the point is a hard ceiling on resident bytes, not a best-effort tail.
|
|
660
|
+
const chunkBytes = Buffer.byteLength(text, 'utf8');
|
|
661
|
+
if (droppedStdoutBytes > 0 || bufferedStdoutBytes + chunkBytes > ctx.bufferedStdoutMaxBytes) {
|
|
662
|
+
droppedStdoutBytes += chunkBytes;
|
|
656
663
|
return;
|
|
657
664
|
}
|
|
665
|
+
bufferedStdout += text;
|
|
666
|
+
bufferedStdoutBytes += chunkBytes;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (streamFormat === 'plain') {
|
|
658
670
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
|
|
659
671
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: { type: 'text_delta', delta: text } } }));
|
|
660
672
|
return;
|
|
@@ -702,12 +714,12 @@ export function wireChildLifecycle(ctx) {
|
|
|
702
714
|
feedSessionLine(controlScanBuffer);
|
|
703
715
|
controlScanBuffer = '';
|
|
704
716
|
}
|
|
717
|
+
flushBufferedStdout();
|
|
705
718
|
streamHandler?.flush();
|
|
706
719
|
session.endSession();
|
|
707
720
|
stdoutDiagnostics.flush();
|
|
708
|
-
//
|
|
709
|
-
// `finish()`'s `'end'
|
|
710
|
-
flushBufferedStdout();
|
|
721
|
+
// The buffered flush is queued before `await emitQueue` so its sanitized events are durable
|
|
722
|
+
// ahead of `finish()`'s `'end'`, exactly like every live-path emit already is.
|
|
711
723
|
await emitQueue;
|
|
712
724
|
if (cancelRequested)
|
|
713
725
|
await cancelUnresolvedToolCalls();
|
|
@@ -843,7 +855,10 @@ function wireAcpLifecycle(ctx) {
|
|
|
843
855
|
unsubscribeCancel();
|
|
844
856
|
// Guarded for the same reasons as the child-driven handler above.
|
|
845
857
|
await cleanupStagedFilesSafely(ctx);
|
|
846
|
-
|
|
858
|
+
let finalStatus = 'cancelled';
|
|
859
|
+
if (!cancelRequested)
|
|
860
|
+
finalStatus = controller?.completedSuccessfully() ? 'succeeded' : 'failed';
|
|
861
|
+
const status = await output.settle({ status: finalStatus });
|
|
847
862
|
const resumable = status === 'failed' && classifyFailure !== undefined
|
|
848
863
|
? await classifyFailureSafely(ctx, classifyFailure, {
|
|
849
864
|
runId,
|
|
@@ -870,7 +885,7 @@ function wireAcpLifecycle(ctx) {
|
|
|
870
885
|
} });
|
|
871
886
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
|
|
872
887
|
}
|
|
873
|
-
|
|
888
|
+
if (translation.kind === 'error') {
|
|
874
889
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
|
|
875
890
|
}
|
|
876
891
|
return;
|
|
@@ -942,7 +957,10 @@ function wirePiRpcLifecycle(ctx) {
|
|
|
942
957
|
unsubscribeCancel();
|
|
943
958
|
// Guarded for the same reasons as the child-driven handler above.
|
|
944
959
|
await cleanupStagedFilesSafely(ctx);
|
|
945
|
-
|
|
960
|
+
let finalStatus = 'cancelled';
|
|
961
|
+
if (!cancelRequested)
|
|
962
|
+
finalStatus = session?.hasFatalError() ? 'failed' : 'succeeded';
|
|
963
|
+
const status = await output.settle({ status: finalStatus });
|
|
946
964
|
const resumable = status === 'failed' && classifyFailure !== undefined
|
|
947
965
|
? await classifyFailureSafely(ctx, classifyFailure, {
|
|
948
966
|
runId,
|
|
@@ -961,18 +979,14 @@ function wirePiRpcLifecycle(ctx) {
|
|
|
961
979
|
const receipt = modelReceipt(translation.payload);
|
|
962
980
|
if (receipt)
|
|
963
981
|
enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
else if ((translation.payload.type === 'text_delta' || translation.payload.type === 'thinking_delta') &&
|
|
970
|
-
translation.payload.delta.length > 0) {
|
|
971
|
-
userVisibleOutputSeen = true;
|
|
972
|
-
}
|
|
982
|
+
applyAgentTranslationSideEffects({ payload: translation.payload, sessionId: translation.sessionId, sink: {
|
|
983
|
+
onSessionId: ({ sessionId }) => { capturedSessionId = sessionId; },
|
|
984
|
+
onToolCall: () => { toolCallSeen = true; },
|
|
985
|
+
onUserVisibleOutput: () => { userVisibleOutputSeen = true; },
|
|
986
|
+
} });
|
|
973
987
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
|
|
974
988
|
}
|
|
975
|
-
|
|
989
|
+
if (translation.kind === 'error') {
|
|
976
990
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
|
|
977
991
|
}
|
|
978
992
|
} }, { cwd: ctx.cwd, ...(ctx.model !== undefined ? { model: ctx.model } : {}), ...(ctx.imagePaths.length > 0 ? { imagePaths: [...ctx.imagePaths] } : {}), ...(ctx.uploadRoot !== undefined ? { uploadRoot: ctx.uploadRoot } : {}) });
|
|
@@ -1047,8 +1061,8 @@ export async function runAcpDispatch({ input, deps }) {
|
|
|
1047
1061
|
// fired-and-forgotten (SEC-007: "await where lifecycle ordering allows it").
|
|
1048
1062
|
await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'acp-attach-failure', deps.onCleanupFailure);
|
|
1049
1063
|
await deps.releaseStagedResources();
|
|
1050
|
-
// `failBeforeSpawn` always rejects (`Promise<never>`);
|
|
1051
|
-
|
|
1064
|
+
// `failBeforeSpawn` always rejects (`Promise<never>`); awaiting it propagates that rejection.
|
|
1065
|
+
await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach ACP session for agent "${input.agentId}": ${errorMessage(err)}` });
|
|
1052
1066
|
}
|
|
1053
1067
|
}
|
|
1054
1068
|
/** Phase 15 (pi-rpc branch): same discipline as {@link runAcpDispatch}, for the one `'pi-rpc'` def. */
|
|
@@ -1080,7 +1094,7 @@ export async function runPiRpcDispatch({ input, deps }) {
|
|
|
1080
1094
|
// fire-and-forget (SEC-007).
|
|
1081
1095
|
await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'pi-rpc-attach-failure', deps.onCleanupFailure);
|
|
1082
1096
|
await deps.releaseStagedResources();
|
|
1083
|
-
|
|
1097
|
+
await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach pi-rpc session for agent "${input.agentId}": ${errorMessage(err)}` });
|
|
1084
1098
|
}
|
|
1085
1099
|
}
|
|
1086
1100
|
//# sourceMappingURL=events.js.map
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*
|
|
12
12
|
* ## v1 scope: all 24 registered agent defs
|
|
13
13
|
*
|
|
14
|
-
* `@jini-ai/agent-runtime`'s registry ships
|
|
15
|
-
* `streamFormat` families. The JSON-stream-parser family — the
|
|
14
|
+
* `@jini-ai/agent-runtime`'s registry ships built-in defs across four
|
|
15
|
+
* `streamFormat` families. The JSON-stream-parser family — the three
|
|
16
16
|
* `createXStreamHandler`-shaped parsers (`claude-stream-json`,
|
|
17
|
-
* `json-event-stream`, `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* `json-event-stream`, `qoder-stream-json`), covering
|
|
18
|
+
* 8 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
|
|
19
|
+
* qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
|
|
20
20
|
* def (`pi`), are wired here. ACP and pi-rpc each own their own JSON-RPC
|
|
21
21
|
* prompt-delivery protocol, so each takes its own lifecycle branch rather
|
|
22
22
|
* than being treated as a stdout-tail parser; pi-rpc's events arrive through
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*
|
|
12
12
|
* ## v1 scope: all 24 registered agent defs
|
|
13
13
|
*
|
|
14
|
-
* `@jini-ai/agent-runtime`'s registry ships
|
|
15
|
-
* `streamFormat` families. The JSON-stream-parser family — the
|
|
14
|
+
* `@jini-ai/agent-runtime`'s registry ships built-in defs across four
|
|
15
|
+
* `streamFormat` families. The JSON-stream-parser family — the three
|
|
16
16
|
* `createXStreamHandler`-shaped parsers (`claude-stream-json`,
|
|
17
|
-
* `json-event-stream`, `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* `json-event-stream`, `qoder-stream-json`), covering
|
|
18
|
+
* 8 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
|
|
19
|
+
* qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
|
|
20
20
|
* def (`pi`), are wired here. ACP and pi-rpc each own their own JSON-RPC
|
|
21
21
|
* prompt-delivery protocol, so each takes its own lifecycle branch rather
|
|
22
22
|
* than being treated as a stdout-tail parser; pi-rpc's events arrive through
|