@namzu/sdk 40.0.0 → 41.0.0
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 +177 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +8 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +11 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/connector/index.d.ts +2 -2
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/adapter.d.ts.map +1 -1
- package/dist/connector/mcp/adapter.js +92 -4
- package/dist/connector/mcp/adapter.js.map +1 -1
- package/dist/connector/mcp/audio-admission.d.ts +17 -0
- package/dist/connector/mcp/audio-admission.d.ts.map +1 -0
- package/dist/connector/mcp/audio-admission.js +171 -0
- package/dist/connector/mcp/audio-admission.js.map +1 -0
- package/dist/connector/mcp/client.d.ts +252 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +611 -39
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/envelope.d.ts +91 -0
- package/dist/connector/mcp/envelope.d.ts.map +1 -0
- package/dist/connector/mcp/envelope.js +173 -0
- package/dist/connector/mcp/envelope.js.map +1 -0
- package/dist/connector/mcp/era.d.ts +130 -0
- package/dist/connector/mcp/era.d.ts.map +1 -0
- package/dist/connector/mcp/era.js +304 -0
- package/dist/connector/mcp/era.js.map +1 -0
- package/dist/connector/mcp/errors.d.ts +106 -0
- package/dist/connector/mcp/errors.d.ts.map +1 -0
- package/dist/connector/mcp/errors.js +154 -0
- package/dist/connector/mcp/errors.js.map +1 -0
- package/dist/connector/mcp/http-sse.d.ts +11 -0
- package/dist/connector/mcp/http-sse.d.ts.map +1 -1
- package/dist/connector/mcp/http-sse.js +21 -6
- package/dist/connector/mcp/http-sse.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +7 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +10 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts +83 -0
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
- package/dist/connector/mcp/streamable-http.js +177 -11
- package/dist/connector/mcp/streamable-http.js.map +1 -1
- package/dist/connector/mcp/x-mcp-header.d.ts +56 -0
- package/dist/connector/mcp/x-mcp-header.d.ts.map +1 -0
- package/dist/connector/mcp/x-mcp-header.js +254 -0
- package/dist/connector/mcp/x-mcp-header.js.map +1 -0
- package/dist/constants/mcp/index.d.ts +123 -15
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +135 -16
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +23 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +20 -0
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +19 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/prompt/index.d.ts +1 -1
- package/dist/prompt/index.d.ts.map +1 -1
- package/dist/prompt/index.js +1 -1
- package/dist/prompt/index.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/sandbox/provider/local.d.ts.map +1 -1
- package/dist/sandbox/provider/local.js +46 -3
- package/dist/sandbox/provider/local.js.map +1 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +8 -0
- package/dist/scheduler/local.js.map +1 -1
- package/dist/store/run/disk.d.ts +35 -1
- package/dist/store/run/disk.d.ts.map +1 -1
- package/dist/store/run/disk.js +100 -0
- package/dist/store/run/disk.js.map +1 -1
- package/dist/types/agent/scheduler.d.ts +20 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +20 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/connector/mcp.d.ts +205 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +56 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/store.d.ts +41 -0
- package/dist/types/run/store.d.ts.map +1 -1
- package/dist/types/sandbox/index.d.ts +68 -1
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bridge/a2a/mapper.ts +8 -0
- package/src/bridge/sse/mapper.ts +11 -0
- package/src/connector/index.ts +27 -0
- package/src/connector/mcp/adapter.ts +103 -4
- package/src/connector/mcp/audio-admission.ts +173 -0
- package/src/connector/mcp/client.ts +694 -45
- package/src/connector/mcp/envelope.ts +235 -0
- package/src/connector/mcp/era.ts +400 -0
- package/src/connector/mcp/errors.ts +171 -0
- package/src/connector/mcp/http-sse.ts +23 -6
- package/src/connector/mcp/index.ts +37 -0
- package/src/connector/mcp/streamable-http.ts +199 -11
- package/src/connector/mcp/x-mcp-header.ts +322 -0
- package/src/constants/mcp/index.ts +145 -16
- package/src/manager/agent/lifecycle.ts +29 -0
- package/src/prompt/coding-agent-doctrine.ts +31 -4
- package/src/prompt/index.ts +1 -0
- package/src/public-runtime.ts +28 -0
- package/src/sandbox/provider/local.ts +45 -2
- package/src/scheduler/local.ts +8 -0
- package/src/store/run/disk.ts +108 -0
- package/src/types/agent/scheduler.ts +21 -0
- package/src/types/agent/task.ts +21 -0
- package/src/types/connector/mcp.ts +205 -1
- package/src/types/run/events.ts +56 -0
- package/src/types/run/store.ts +42 -0
- package/src/types/sandbox/index.ts +69 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
readFile as fsReadFile,
|
|
5
5
|
writeFile as fsWriteFile,
|
|
6
6
|
mkdir,
|
|
7
|
+
open,
|
|
7
8
|
readdir,
|
|
8
9
|
rename,
|
|
9
10
|
rm,
|
|
@@ -38,6 +39,7 @@ import type {
|
|
|
38
39
|
SandboxFileEntry,
|
|
39
40
|
SandboxIsolationControl,
|
|
40
41
|
SandboxProvider,
|
|
42
|
+
SandboxReadFileOptions,
|
|
41
43
|
SandboxSpawnOptions,
|
|
42
44
|
SandboxStatus,
|
|
43
45
|
SandboxWalkFilesOptions,
|
|
@@ -742,13 +744,54 @@ class LocalSandbox implements Sandbox {
|
|
|
742
744
|
this.log.debug('File written', { 'namzu.sandbox.path': resolved })
|
|
743
745
|
}
|
|
744
746
|
|
|
745
|
-
|
|
747
|
+
/**
|
|
748
|
+
* `options.offset`/`options.length` are HONOURED, not ignored.
|
|
749
|
+
*
|
|
750
|
+
* {@link Sandbox.readFile} is explicit that a backend which takes the
|
|
751
|
+
* parameter and answers with the whole file has given a WRONG answer
|
|
752
|
+
* rather than a degraded one, and must reject instead. On a local
|
|
753
|
+
* filesystem there is nothing to reject: a slice is one positional read,
|
|
754
|
+
* so this serves it.
|
|
755
|
+
*
|
|
756
|
+
* A range that runs past the end returns the bytes that exist, because a
|
|
757
|
+
* caller resuming from a remembered offset cannot know the answer before
|
|
758
|
+
* it asks.
|
|
759
|
+
*
|
|
760
|
+
* `options.signal` cannot be handed to a positional read — `FileHandle`
|
|
761
|
+
* takes none — so it is checked on both sides of one bounded slice
|
|
762
|
+
* instead. That honours the contract's "aborts the read" as far as a
|
|
763
|
+
* local disk allows: an abort is never answered with data.
|
|
764
|
+
*/
|
|
765
|
+
async readFile(path: string, options?: SandboxReadFileOptions): Promise<Buffer> {
|
|
746
766
|
if (this._status === 'destroyed') {
|
|
747
767
|
throw new Error(`Sandbox ${this.id} is destroyed`)
|
|
748
768
|
}
|
|
749
769
|
|
|
750
770
|
const resolved = await resolveWithinAnyReal(this.roots, path)
|
|
751
|
-
|
|
771
|
+
const { offset, length, signal } = options ?? {}
|
|
772
|
+
signal?.throwIfAborted()
|
|
773
|
+
if (offset === undefined && length === undefined) {
|
|
774
|
+
return await fsReadFile(resolved, signal ? { signal } : undefined)
|
|
775
|
+
}
|
|
776
|
+
if (offset !== undefined && (!Number.isSafeInteger(offset) || offset < 0)) {
|
|
777
|
+
throw new Error('readFile: offset must be a non-negative safe integer')
|
|
778
|
+
}
|
|
779
|
+
if (length !== undefined && (!Number.isSafeInteger(length) || length < 0)) {
|
|
780
|
+
throw new Error('readFile: length must be a non-negative safe integer')
|
|
781
|
+
}
|
|
782
|
+
const handle = await open(resolved, 'r')
|
|
783
|
+
try {
|
|
784
|
+
const from = offset ?? 0
|
|
785
|
+
const remaining = Math.max(0, (await handle.stat()).size - from)
|
|
786
|
+
const want = Math.min(length ?? remaining, remaining)
|
|
787
|
+
if (want === 0) return Buffer.alloc(0)
|
|
788
|
+
const buf = Buffer.allocUnsafe(want)
|
|
789
|
+
const { bytesRead } = await handle.read(buf, 0, want, from)
|
|
790
|
+
signal?.throwIfAborted()
|
|
791
|
+
return buf.subarray(0, bytesRead)
|
|
792
|
+
} finally {
|
|
793
|
+
await handle.close().catch(() => undefined)
|
|
794
|
+
}
|
|
752
795
|
}
|
|
753
796
|
|
|
754
797
|
async listFiles(rootPath: string): Promise<readonly SandboxFileEntry[]> {
|
package/src/scheduler/local.ts
CHANGED
|
@@ -114,6 +114,14 @@ export class LocalTaskScheduler implements TaskScheduler {
|
|
|
114
114
|
beforeStart: options.beforeStart,
|
|
115
115
|
...(options.planId ? { planId: options.planId } : {}),
|
|
116
116
|
...(options.planStepId ? { planStepId: options.planStepId } : {}),
|
|
117
|
+
// Display grouping travels with the spawn so the manager can put it
|
|
118
|
+
// on `agent_pending`. Spread conditionally, like the plan edge
|
|
119
|
+
// above: a host that groups nothing must not be made to look like
|
|
120
|
+
// one that grouped everything under an empty label.
|
|
121
|
+
...(options.workflow ? { workflow: options.workflow } : {}),
|
|
122
|
+
...(options.phase ? { phase: options.phase } : {}),
|
|
123
|
+
...(options.phaseDetail ? { phaseDetail: options.phaseDetail } : {}),
|
|
124
|
+
...(options.phaseOrder !== undefined ? { phaseOrder: options.phaseOrder } : {}),
|
|
117
125
|
input: {
|
|
118
126
|
messages: [createUserMessage(options.prompt)],
|
|
119
127
|
workingDirectory: options.workingDirectory,
|
package/src/store/run/disk.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { appendFile, mkdir, readFile, readdir, stat, unlink } from 'node:fs/promises'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
+
import type { RunExecutionStatus } from '../../types/common/index.js'
|
|
4
5
|
import type { CheckpointId, IterationCheckpoint } from '../../types/hitl/index.js'
|
|
5
6
|
import type { Message } from '../../types/message/index.js'
|
|
6
7
|
import type {
|
|
@@ -12,6 +13,7 @@ import type {
|
|
|
12
13
|
} from '../../types/run/index.js'
|
|
13
14
|
import type {
|
|
14
15
|
CompletedToolRecord,
|
|
16
|
+
DelegatedChildRun,
|
|
15
17
|
ReadRunEventsOptions,
|
|
16
18
|
RunMessageSnapshot,
|
|
17
19
|
RunStore,
|
|
@@ -432,6 +434,86 @@ export class RunDiskStore implements RunStore {
|
|
|
432
434
|
}
|
|
433
435
|
}
|
|
434
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Every delegated child run saved under one parent, oldest first.
|
|
439
|
+
*
|
|
440
|
+
* The sibling of {@link RunDiskStore.listRuns}, and deliberately not a fix
|
|
441
|
+
* to it. `addToIndex` returns early for any run with a `parentRunId`, which
|
|
442
|
+
* is what keeps delegated children out of `index.json` and therefore out of
|
|
443
|
+
* a host's conversation listing — a child is not a conversation anyone
|
|
444
|
+
* resumes, and putting one there would offer to continue work whose parent
|
|
445
|
+
* turn is long over. That guard stays. This walks the `children/` directory
|
|
446
|
+
* instead, so the evidence a child already wrote is reachable by something
|
|
447
|
+
* that came looking for it, without any of it becoming resumable.
|
|
448
|
+
*
|
|
449
|
+
* READ-ONLY, and that matters more here than for most reads: binding a
|
|
450
|
+
* {@link RunDiskStore} to a run CREATES its directory, so discovery had to
|
|
451
|
+
* be a free walk or it would mint the very directories it claims to find.
|
|
452
|
+
* Nothing here writes, moves or prunes.
|
|
453
|
+
*
|
|
454
|
+
* Tolerant of half-written evidence, the same way the transcript reader
|
|
455
|
+
* next door is. A child directory with no `run.json` — a run killed before
|
|
456
|
+
* its terminal write — is SKIPPED rather than reported with invented
|
|
457
|
+
* fields, and so is one whose `run.json` is not readable JSON. What is
|
|
458
|
+
* skipped is the listing row, not the directory: a caller that knows the
|
|
459
|
+
* run id can still read the transcript beside it.
|
|
460
|
+
*
|
|
461
|
+
* Oldest first, by `startedAt`, so the order matches the order the parent
|
|
462
|
+
* launched them. A child whose `run.json` never recorded a start sorts
|
|
463
|
+
* first; there is no later moment to claim for it.
|
|
464
|
+
*
|
|
465
|
+
* `baseDir` is the runs directory the parent was written under — the same
|
|
466
|
+
* {@link RunStoreConfig.baseDir} the child's store had, which is why one
|
|
467
|
+
* parent's children can be spread across several of them when a host gives
|
|
468
|
+
* each child its own session directory.
|
|
469
|
+
*/
|
|
470
|
+
static async listChildren(
|
|
471
|
+
baseDir: string,
|
|
472
|
+
parentRunId: string,
|
|
473
|
+
): Promise<readonly DelegatedChildRun[]> {
|
|
474
|
+
asRunId(parentRunId)
|
|
475
|
+
const childrenDir = join(baseDir, parentRunId, 'children')
|
|
476
|
+
let names: string[]
|
|
477
|
+
try {
|
|
478
|
+
names = await readdir(childrenDir)
|
|
479
|
+
} catch (err) {
|
|
480
|
+
if (isFileNotFound(err) || isNotADirectory(err)) return []
|
|
481
|
+
throw err
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const children: DelegatedChildRun[] = []
|
|
485
|
+
for (const name of names) {
|
|
486
|
+
const dir = join(childrenDir, name)
|
|
487
|
+
let meta: unknown
|
|
488
|
+
try {
|
|
489
|
+
meta = JSON.parse(await readFile(join(dir, 'run.json'), 'utf-8'))
|
|
490
|
+
} catch (err) {
|
|
491
|
+
// ENOTDIR covers a stray file sitting beside the child directories.
|
|
492
|
+
if (isFileNotFound(err) || isNotADirectory(err) || err instanceof SyntaxError) continue
|
|
493
|
+
throw err
|
|
494
|
+
}
|
|
495
|
+
if (meta === null || typeof meta !== 'object') continue
|
|
496
|
+
const record = meta as Record<string, unknown>
|
|
497
|
+
const metadata = asRecord(record.metadata)
|
|
498
|
+
const config = asRecord(metadata?.config)
|
|
499
|
+
const usage = asRecord(record.tokenUsage)
|
|
500
|
+
children.push({
|
|
501
|
+
id: name,
|
|
502
|
+
parentRunId,
|
|
503
|
+
dir,
|
|
504
|
+
...(typeof metadata?.agentId === 'string' ? { agentId: metadata.agentId } : {}),
|
|
505
|
+
...(typeof metadata?.agentName === 'string' ? { agentName: metadata.agentName } : {}),
|
|
506
|
+
...(typeof config?.model === 'string' ? { model: config.model } : {}),
|
|
507
|
+
...(isRunExecutionStatus(record.status) ? { status: record.status } : {}),
|
|
508
|
+
...(typeof record.startedAt === 'number' ? { startedAt: record.startedAt } : {}),
|
|
509
|
+
...(typeof record.endedAt === 'number' ? { endedAt: record.endedAt } : {}),
|
|
510
|
+
...(typeof usage?.totalTokens === 'number' ? { totalTokens: usage.totalTokens } : {}),
|
|
511
|
+
...(typeof record.depth === 'number' ? { depth: record.depth } : {}),
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
return children.sort((left, right) => (left.startedAt ?? 0) - (right.startedAt ?? 0))
|
|
515
|
+
}
|
|
516
|
+
|
|
435
517
|
async addToIndex(run: Run): Promise<void> {
|
|
436
518
|
if (run.parentRunId) return
|
|
437
519
|
|
|
@@ -798,6 +880,32 @@ function parseCheckpoint(content: string, file: string): IterationCheckpoint {
|
|
|
798
880
|
return record as IterationCheckpoint
|
|
799
881
|
}
|
|
800
882
|
|
|
883
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
884
|
+
return typeof value === 'object' && value !== null
|
|
885
|
+
? (value as Record<string, unknown>)
|
|
886
|
+
: undefined
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const RUN_EXECUTION_STATUSES: readonly RunExecutionStatus[] = [
|
|
890
|
+
'idle',
|
|
891
|
+
'pending',
|
|
892
|
+
'running',
|
|
893
|
+
'completed',
|
|
894
|
+
'failed',
|
|
895
|
+
'cancelled',
|
|
896
|
+
]
|
|
897
|
+
|
|
898
|
+
function isRunExecutionStatus(value: unknown): value is RunExecutionStatus {
|
|
899
|
+
return typeof value === 'string' && RUN_EXECUTION_STATUSES.includes(value as RunExecutionStatus)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/** A path component that is a file where a directory was expected. */
|
|
903
|
+
function isNotADirectory(err: unknown): boolean {
|
|
904
|
+
return (
|
|
905
|
+
typeof err === 'object' && err !== null && (err as NodeJS.ErrnoException).code === 'ENOTDIR'
|
|
906
|
+
)
|
|
907
|
+
}
|
|
908
|
+
|
|
801
909
|
function isFileNotFound(err: unknown): boolean {
|
|
802
910
|
return typeof err === 'object' && err !== null && (err as NodeJS.ErrnoException).code === 'ENOENT'
|
|
803
911
|
}
|
|
@@ -58,6 +58,27 @@ export interface CreateTaskOptions {
|
|
|
58
58
|
readonly planId?: string
|
|
59
59
|
readonly planStepId?: string
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Display grouping for the delegated child, carried onto its
|
|
63
|
+
* `agent_pending` event so a consumer watching from outside this process
|
|
64
|
+
* can group the child the way this caller meant. Reach, not durability:
|
|
65
|
+
* that event goes straight to a host's listener and enters no run's log,
|
|
66
|
+
* so nothing here is persisted by the kernel. See the `agent_pending`
|
|
67
|
+
* variant in `types/run/events.ts` for the full contract.
|
|
68
|
+
*
|
|
69
|
+
* These fields are display annotations only; they do not create
|
|
70
|
+
* dependencies, barriers, or serial execution. The kernel reads none of
|
|
71
|
+
* them — a caller wanting correlation a host may act on has
|
|
72
|
+
* {@link planId} and {@link planStepId} for that.
|
|
73
|
+
*/
|
|
74
|
+
readonly workflow?: string
|
|
75
|
+
/** Stage within {@link workflow}. Display-only on the same terms. */
|
|
76
|
+
readonly phase?: string
|
|
77
|
+
/** Longer text explaining {@link phase}. Display-only on the same terms. */
|
|
78
|
+
readonly phaseDetail?: string
|
|
79
|
+
/** Zero-based DISPLAY order for {@link phase}. Display-only on the same terms. */
|
|
80
|
+
readonly phaseOrder?: number
|
|
81
|
+
|
|
61
82
|
agentId: string
|
|
62
83
|
|
|
63
84
|
/**
|
package/src/types/agent/task.ts
CHANGED
|
@@ -158,6 +158,27 @@ export interface SendMessageOptions {
|
|
|
158
158
|
readonly planId?: string
|
|
159
159
|
readonly planStepId?: string
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Display grouping for the delegated child, carried onto its
|
|
163
|
+
* `agent_pending` event so a consumer watching from outside this process
|
|
164
|
+
* can group the child the way this caller meant. Reach, not durability:
|
|
165
|
+
* that event goes straight to a host's listener and enters no run's log,
|
|
166
|
+
* so nothing here is persisted by the kernel. See the `agent_pending`
|
|
167
|
+
* variant in `types/run/events.ts` for the full contract.
|
|
168
|
+
*
|
|
169
|
+
* These fields are display annotations only; they do not create
|
|
170
|
+
* dependencies, barriers, or serial execution. The kernel reads none of
|
|
171
|
+
* them — a caller wanting correlation a host may act on has
|
|
172
|
+
* {@link planId} and {@link planStepId} for that.
|
|
173
|
+
*/
|
|
174
|
+
readonly workflow?: string
|
|
175
|
+
/** Stage within {@link workflow}. Display-only on the same terms. */
|
|
176
|
+
readonly phase?: string
|
|
177
|
+
/** Longer text explaining {@link phase}. Display-only on the same terms. */
|
|
178
|
+
readonly phaseDetail?: string
|
|
179
|
+
/** Zero-based DISPLAY order for {@link phase}. Display-only on the same terms. */
|
|
180
|
+
readonly phaseOrder?: number
|
|
181
|
+
|
|
161
182
|
agentId: string
|
|
162
183
|
|
|
163
184
|
input: AgentInput
|
|
@@ -40,11 +40,40 @@ export interface MCPStdioTransportConfig extends MCPTransportConfigBase {
|
|
|
40
40
|
cwd?: string
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Anything that answers like `fetch`, restricted to the request shape the
|
|
45
|
+
* MCP HTTP transports actually send: a URL string, an optional
|
|
46
|
+
* method/headers/body, `redirect` (both transports pin this to `'manual'`
|
|
47
|
+
* so a caller cannot silently re-enable auto-following a redirect), and an
|
|
48
|
+
* abort signal.
|
|
49
|
+
*
|
|
50
|
+
* Structurally identical in spirit to `bridge/a2a/client.ts`'s `FetchLike`
|
|
51
|
+
* — the same injectable, socket-free function shape, so a test needs no
|
|
52
|
+
* socket — but the return type stays the real `Response` rather than that
|
|
53
|
+
* bridge's narrower `{ok, status, json(), text()}` duck type: both MCP
|
|
54
|
+
* transports already read `.headers` (content type, session id) and one of
|
|
55
|
+
* them reads `.body` as a stream (the SSE GET), neither of which the A2A
|
|
56
|
+
* bridge's version exposes. Re-declared here, rather than imported from the
|
|
57
|
+
* A2A bridge, so that bridge is not forced to grow fields it does not use.
|
|
58
|
+
*/
|
|
59
|
+
export type MCPFetchLike = (
|
|
60
|
+
input: string,
|
|
61
|
+
init?: {
|
|
62
|
+
method?: string
|
|
63
|
+
headers?: Record<string, string>
|
|
64
|
+
body?: string
|
|
65
|
+
redirect?: 'manual' | 'follow' | 'error'
|
|
66
|
+
signal?: AbortSignal
|
|
67
|
+
},
|
|
68
|
+
) => Promise<Response>
|
|
69
|
+
|
|
43
70
|
export interface MCPHttpSseTransportConfig extends MCPTransportConfigBase {
|
|
44
71
|
type: 'http-sse'
|
|
45
72
|
url: string
|
|
46
73
|
headers?: Record<string, string>
|
|
47
74
|
timeoutMs?: number
|
|
75
|
+
/** Injected in place of the ambient global `fetch`. Defaults to it. */
|
|
76
|
+
fetch?: MCPFetchLike
|
|
48
77
|
}
|
|
49
78
|
|
|
50
79
|
export interface MCPStreamableHttpTransportConfig extends MCPTransportConfigBase {
|
|
@@ -52,6 +81,8 @@ export interface MCPStreamableHttpTransportConfig extends MCPTransportConfigBase
|
|
|
52
81
|
url: string
|
|
53
82
|
headers?: Record<string, string>
|
|
54
83
|
timeoutMs?: number
|
|
84
|
+
/** Injected in place of the ambient global `fetch`. Defaults to it. */
|
|
85
|
+
fetch?: MCPFetchLike
|
|
55
86
|
}
|
|
56
87
|
|
|
57
88
|
export type MCPTransportUnion =
|
|
@@ -65,6 +96,79 @@ export interface MCPJsonRpcError {
|
|
|
65
96
|
data?: unknown
|
|
66
97
|
}
|
|
67
98
|
|
|
99
|
+
/**
|
|
100
|
+
* A protocol revision this client can still negotiate DOWN to when a server
|
|
101
|
+
* does not speak the current spec, newest first.
|
|
102
|
+
*
|
|
103
|
+
* Kept as a literal union (rather than just `string`) so a caller pattern
|
|
104
|
+
* matching on `McpEra` gets real exhaustiveness checking; the runtime array
|
|
105
|
+
* of the same values lives in `constants/mcp` and is typed against this.
|
|
106
|
+
*/
|
|
107
|
+
export type McpLegacyVersion = '2025-11-25' | '2025-06-18' | '2025-03-26' | '2024-11-05'
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A protocol revision this client speaks WITHOUT the `initialize`
|
|
111
|
+
* handshake.
|
|
112
|
+
*
|
|
113
|
+
* A modern connection is stateless: there is no handshake, no session id,
|
|
114
|
+
* and every request carries its own protocol version, client capabilities
|
|
115
|
+
* and client info in `_meta`. `connect()` probes for one before it offers
|
|
116
|
+
* the legacy handshake.
|
|
117
|
+
*/
|
|
118
|
+
export type McpModernVersion = '2026-07-28'
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Which family of the wire protocol a connection resolved to, and which
|
|
122
|
+
* exact revision within it.
|
|
123
|
+
*
|
|
124
|
+
* `kind` alone tells a caller which rules apply — whether `_meta` and the
|
|
125
|
+
* stateless per-request shape are in play, or the `initialize` handshake
|
|
126
|
+
* and (for 2025-06-18 and later) the `MCP-Protocol-Version` header — without
|
|
127
|
+
* re-deriving it from the version string on every read.
|
|
128
|
+
*
|
|
129
|
+
* `MCPClient.connect()` resolves this by probing for a modern peer first
|
|
130
|
+
* and falling back to the legacy `initialize` handshake, so which arm a
|
|
131
|
+
* given connection lands on is the server's answer, not a configuration.
|
|
132
|
+
*/
|
|
133
|
+
export type McpEra =
|
|
134
|
+
| { readonly kind: 'modern'; readonly version: McpModernVersion }
|
|
135
|
+
| { readonly kind: 'legacy'; readonly version: McpLegacyVersion }
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* What a modern server answers `server/discover` with.
|
|
139
|
+
*
|
|
140
|
+
* The modern era's replacement for the `initialize` result: it names the
|
|
141
|
+
* revisions the server speaks, what it can do, and — under the reserved
|
|
142
|
+
* `_meta` key — who it is. Every field is optional on the wire as far as
|
|
143
|
+
* this client is concerned, because the one thing it MUST be able to do
|
|
144
|
+
* with a malformed answer is decline to treat it as proof of a modern peer.
|
|
145
|
+
*/
|
|
146
|
+
export interface MCPDiscoverResult {
|
|
147
|
+
/** Newest first is conventional but not required; this client sorts. */
|
|
148
|
+
supportedVersions?: readonly string[]
|
|
149
|
+
capabilities?: MCPServerCapabilities
|
|
150
|
+
_meta?: Record<string, unknown>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Where a resolved {@link McpEra} is remembered between connections.
|
|
155
|
+
*
|
|
156
|
+
* The spec's own guidance: a client SHOULD cache the era for the lifetime
|
|
157
|
+
* of the server process (stdio) or the origin (HTTP) and re-probe if the
|
|
158
|
+
* cached assumption later fails. Without it every connection to a legacy
|
|
159
|
+
* server pays a wasted probe round trip.
|
|
160
|
+
*
|
|
161
|
+
* An interface rather than a module-level `Map` because a process-global
|
|
162
|
+
* cache leaks between tests and would make a conformance suite depend on
|
|
163
|
+
* the order its cases happen to run in. `MCPClientConfig.eraCache` injects
|
|
164
|
+
* one; omitting it uses a process-wide default.
|
|
165
|
+
*/
|
|
166
|
+
export interface MCPEraCache {
|
|
167
|
+
get(key: string): McpEra | undefined
|
|
168
|
+
set(key: string, era: McpEra): void
|
|
169
|
+
delete(key: string): void
|
|
170
|
+
}
|
|
171
|
+
|
|
68
172
|
export interface MCPJsonRpcMessage {
|
|
69
173
|
jsonrpc: '2.0'
|
|
70
174
|
id?: string | number
|
|
@@ -83,12 +187,48 @@ export interface MCPRequestOptions {
|
|
|
83
187
|
* waiting, not that an already-started remote side effect was rolled back.
|
|
84
188
|
*/
|
|
85
189
|
readonly signal?: AbortSignal
|
|
190
|
+
/**
|
|
191
|
+
* Extra headers for this one request, merged over the transport's static
|
|
192
|
+
* config headers (a collision resolves to this value) and under this same
|
|
193
|
+
* call's `bearerToken`, if both are given.
|
|
194
|
+
*
|
|
195
|
+
* The protocol's own headers are the exception: `MCP-Protocol-Version`,
|
|
196
|
+
* `Mcp-Method` and `Mcp-Name` mirror values inside the request this call
|
|
197
|
+
* is sending, and a server rejects a header that disagrees with the body
|
|
198
|
+
* it mirrors. A value given here under one of those names — matched
|
|
199
|
+
* without regard to case — is refused and warn-logged rather than put on
|
|
200
|
+
* the wire.
|
|
201
|
+
*
|
|
202
|
+
* A transport with no header concept (stdio) receives the field and does
|
|
203
|
+
* nothing with it.
|
|
204
|
+
*/
|
|
205
|
+
readonly headers?: Readonly<Record<string, string>>
|
|
206
|
+
/**
|
|
207
|
+
* Sent as `Authorization: Bearer <bearerToken>` on this one request.
|
|
208
|
+
*
|
|
209
|
+
* Overrides a configured `Authorization` header — static or supplied via
|
|
210
|
+
* `headers` above — for this request only; it never touches a
|
|
211
|
+
* differently-named header such as a static `X-API-Key`. Omit it and a
|
|
212
|
+
* configured `Authorization` header is left exactly as configured.
|
|
213
|
+
*/
|
|
214
|
+
readonly bearerToken?: string
|
|
86
215
|
}
|
|
87
216
|
|
|
88
217
|
/** Authority for one transport write and any response body it consumes. */
|
|
89
218
|
export interface MCPTransportSendOptions {
|
|
90
219
|
/** A pre-aborted signal starts no transport work. */
|
|
91
220
|
readonly signal?: AbortSignal
|
|
221
|
+
/**
|
|
222
|
+
* Extra headers for this one send.
|
|
223
|
+
*
|
|
224
|
+
* An HTTP-speaking transport merges these over its static config
|
|
225
|
+
* headers; a transport with no header concept (stdio) receives the
|
|
226
|
+
* field and does nothing with it. Introduced so the client — which
|
|
227
|
+
* alone knows the negotiated era — can ask for `MCP-Protocol-Version`
|
|
228
|
+
* on a post-initialize request without the transport having to know
|
|
229
|
+
* what a protocol version is.
|
|
230
|
+
*/
|
|
231
|
+
readonly headers?: Readonly<Record<string, string>>
|
|
92
232
|
}
|
|
93
233
|
|
|
94
234
|
export interface MCPTransport {
|
|
@@ -138,10 +278,38 @@ export interface MCPToolDefinition {
|
|
|
138
278
|
annotations?: MCPToolAnnotations
|
|
139
279
|
}
|
|
140
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Audience/priority/freshness hints a server may attach to a content block,
|
|
283
|
+
* part of the schema since 2025-06-18. Advisory only: namzu does not act on
|
|
284
|
+
* any of these fields today, but drops none of them either — they survive
|
|
285
|
+
* into `ToolResult.data` for a host that wants to read them.
|
|
286
|
+
*
|
|
287
|
+
* Distinct from {@link MCPToolAnnotations}, which describes a TOOL
|
|
288
|
+
* (read-only, destructive, …); this describes one piece of CONTENT.
|
|
289
|
+
*/
|
|
290
|
+
export interface MCPContentAnnotations {
|
|
291
|
+
audience?: Array<'user' | 'assistant'>
|
|
292
|
+
priority?: number
|
|
293
|
+
lastModified?: string
|
|
294
|
+
}
|
|
295
|
+
|
|
141
296
|
export type MCPContentBlock =
|
|
142
297
|
| { type: 'text'; text: string }
|
|
143
298
|
| { type: 'image'; data: string; mimeType: string }
|
|
144
|
-
| {
|
|
299
|
+
| {
|
|
300
|
+
type: 'resource'
|
|
301
|
+
resource: { uri: string; mimeType?: string; text?: string; blob?: string }
|
|
302
|
+
annotations?: MCPContentAnnotations
|
|
303
|
+
}
|
|
304
|
+
/** Since 2025-03-26. Raw audio bytes, base64-encoded like `image`. */
|
|
305
|
+
| { type: 'audio'; data: string; mimeType: string }
|
|
306
|
+
/**
|
|
307
|
+
* Since 2025-06-18. A pointer to a resource the server has NOT embedded
|
|
308
|
+
* inline — unlike `resource`, which always carries `text` or `blob`.
|
|
309
|
+
* Because this block carries no content at all, the adapter names it
|
|
310
|
+
* for the model rather than fabricating text the server never sent.
|
|
311
|
+
*/
|
|
312
|
+
| { type: 'resource_link'; uri: string; name: string; description?: string; mimeType?: string }
|
|
145
313
|
|
|
146
314
|
export interface MCPToolResult {
|
|
147
315
|
content: MCPContentBlock[]
|
|
@@ -160,6 +328,23 @@ export interface MCPToolResult {
|
|
|
160
328
|
_meta?: Record<string, unknown>
|
|
161
329
|
}
|
|
162
330
|
|
|
331
|
+
/**
|
|
332
|
+
* One thing the client would have to do that it never declared it could —
|
|
333
|
+
* elicit input, sample a message, list roots, or something a later spec
|
|
334
|
+
* revision defines. Only `method` is read by this client; every other field
|
|
335
|
+
* is carried opaquely so a shape it does not understand still names itself.
|
|
336
|
+
*
|
|
337
|
+
* namzu declares `clientCapabilities: {}` in every era, so MRTR rule 7 — a
|
|
338
|
+
* server MUST NOT send an `inputRequests` entry for a capability the client
|
|
339
|
+
* did not declare — means a CONFORMING server never produces one of these.
|
|
340
|
+
* The type exists for the defensive path: a non-conforming server's demand
|
|
341
|
+
* is named and refused rather than silently misread as an ordinary result.
|
|
342
|
+
*/
|
|
343
|
+
export interface MCPInputRequest {
|
|
344
|
+
readonly method: string
|
|
345
|
+
readonly [key: string]: unknown
|
|
346
|
+
}
|
|
347
|
+
|
|
163
348
|
export interface MCPResource {
|
|
164
349
|
uri: string
|
|
165
350
|
name: string
|
|
@@ -242,6 +427,25 @@ export interface MCPClientConfig {
|
|
|
242
427
|
* forever — no error, no failure, just a run that stopped.
|
|
243
428
|
*/
|
|
244
429
|
requestTimeoutMs?: number
|
|
430
|
+
/**
|
|
431
|
+
* How long `connect()`'s era probe waits for an answer before deciding
|
|
432
|
+
* the peer speaks a legacy revision. Defaults to
|
|
433
|
+
* `DEFAULT_MCP_ERA_PROBE_TIMEOUT_MS`.
|
|
434
|
+
*
|
|
435
|
+
* Never longer than `requestTimeoutMs`: a probe is a request, and a
|
|
436
|
+
* probe that outlived the deadline every other request is held to would
|
|
437
|
+
* be a connect that hangs past its own timeout.
|
|
438
|
+
*/
|
|
439
|
+
eraProbeTimeoutMs?: number
|
|
440
|
+
/**
|
|
441
|
+
* Where this client reads and records the resolved era.
|
|
442
|
+
*
|
|
443
|
+
* Defaults to a process-wide cache shared by every `MCPClient`, which is
|
|
444
|
+
* the point — two clients reaching the same origin should not each pay a
|
|
445
|
+
* probe. Inject a fresh one to isolate a test, or a longer-lived one to
|
|
446
|
+
* scope the memory to a host rather than the process.
|
|
447
|
+
*/
|
|
448
|
+
eraCache?: MCPEraCache
|
|
245
449
|
/**
|
|
246
450
|
* A pre-built logger. Threaded into the transport `MCPClient` constructs
|
|
247
451
|
* internally (`createTransport`), so a caller that supplies this gets a
|
package/src/types/run/events.ts
CHANGED
|
@@ -812,6 +812,62 @@ type CoreRunEvent =
|
|
|
812
812
|
/** Approved plan edge carried while the blocking tool is still live. */
|
|
813
813
|
planId?: string
|
|
814
814
|
planStepId?: string
|
|
815
|
+
/**
|
|
816
|
+
* How the host that delegated this child wants it GROUPED on screen —
|
|
817
|
+
* a shared label over a set of related delegations, typically one
|
|
818
|
+
* operator-visible piece of work several children are doing together.
|
|
819
|
+
*
|
|
820
|
+
* These fields are display annotations only; they do not create
|
|
821
|
+
* dependencies, barriers, or serial execution. Nothing in the kernel
|
|
822
|
+
* reads them: admission, ordering and concurrency come from the
|
|
823
|
+
* scheduler and from {@link planId}/{@link planStepId}, which is the
|
|
824
|
+
* field pair that DOES carry correlation a host may act on. A reader
|
|
825
|
+
* who infers execution structure from a label here has inferred it
|
|
826
|
+
* from a caption.
|
|
827
|
+
*
|
|
828
|
+
* Absent unless the delegating host supplied them, which is the
|
|
829
|
+
* normal case — a host that groups nothing sends nothing, and a
|
|
830
|
+
* consumer written before these existed reads the same event it
|
|
831
|
+
* always did.
|
|
832
|
+
*
|
|
833
|
+
* They ride this event rather than staying in the delegating
|
|
834
|
+
* process's memory for REACH: a consumer watching from outside
|
|
835
|
+
* that process — another listener, or an SSE client — can rebuild
|
|
836
|
+
* the same picture instead of seeing an undifferentiated list of
|
|
837
|
+
* children.
|
|
838
|
+
*
|
|
839
|
+
* Reach is not durability, and this event buys only the first.
|
|
840
|
+
* Like every delegation lifecycle event, it is handed straight to
|
|
841
|
+
* a host's listener and never enters a run's log — which is what
|
|
842
|
+
* the absent `seq` on this variant says, and what the `seq` doc
|
|
843
|
+
* above spells out. A label here is therefore written nowhere by
|
|
844
|
+
* the kernel and does not survive a restart of the host that chose
|
|
845
|
+
* it; a host wanting the grouping to outlive its process records it
|
|
846
|
+
* from the listener.
|
|
847
|
+
*/
|
|
848
|
+
workflow?: string
|
|
849
|
+
/**
|
|
850
|
+
* Display group WITHIN {@link workflow} — a stage of that work, as
|
|
851
|
+
* the delegating host labelled it. Display-only on the same terms as
|
|
852
|
+
* {@link workflow}: it creates no dependencies, barriers or serial
|
|
853
|
+
* execution, and two children naming the same phase are not thereby
|
|
854
|
+
* sequenced or synchronised.
|
|
855
|
+
*/
|
|
856
|
+
phase?: string
|
|
857
|
+
/**
|
|
858
|
+
* Longer text explaining {@link phase}, for a surface that has room
|
|
859
|
+
* to show it. Display-only on the same terms as {@link workflow}.
|
|
860
|
+
*/
|
|
861
|
+
phaseDetail?: string
|
|
862
|
+
/**
|
|
863
|
+
* Where {@link phase} sits in the host's intended DISPLAY order,
|
|
864
|
+
* zero-based. Display-only on the same terms as {@link workflow}: it
|
|
865
|
+
* orders a list on a screen and orders nothing that runs. Children in
|
|
866
|
+
* one phase are expected to carry the same value; a consumer that
|
|
867
|
+
* sees two disagree should keep the first rather than resequence,
|
|
868
|
+
* because nothing here is authoritative enough to arbitrate.
|
|
869
|
+
*/
|
|
870
|
+
phaseOrder?: number
|
|
815
871
|
}
|
|
816
872
|
| {
|
|
817
873
|
type: 'agent_completed'
|
package/src/types/run/store.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { RunEvidenceScope, RunTextEvidenceSource } from '../../store/eviden
|
|
|
28
28
|
* re-keyed per call, it happens once, deliberately, as its own change.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import type { RunExecutionStatus } from '../common/index.js'
|
|
31
32
|
import type { Message } from '../message/index.js'
|
|
32
33
|
import type { AuditEvent } from './audit.js'
|
|
33
34
|
import type { Run } from './entity.js'
|
|
@@ -103,6 +104,47 @@ export type ToolExecutionRecord =
|
|
|
103
104
|
| (CompletedToolRecord & { readonly status: 'completed' })
|
|
104
105
|
| { readonly toolUseId: string; readonly toolName: string; readonly status: 'started' }
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* One delegated child run found on disk under its parent's `children/`
|
|
109
|
+
* directory, as {@link import('../../store/run/disk.js').RunDiskStore.listChildren}
|
|
110
|
+
* reports it.
|
|
111
|
+
*
|
|
112
|
+
* A DISCOVERY record, not the child's evidence: every field here comes from
|
|
113
|
+
* the child's `run.json`, and the transcript, message snapshot and report
|
|
114
|
+
* beside it stay on disk until something asks for them. {@link dir} is what
|
|
115
|
+
* that something reads from.
|
|
116
|
+
*
|
|
117
|
+
* Everything the file supplies is optional, because a `run.json` is written
|
|
118
|
+
* by the child's own terminal path and a process killed before it got there
|
|
119
|
+
* leaves a directory whose other evidence is still worth opening. An absent
|
|
120
|
+
* field is "this file did not say", never a zero or an empty string.
|
|
121
|
+
*/
|
|
122
|
+
export interface DelegatedChildRun {
|
|
123
|
+
/**
|
|
124
|
+
* The child's run id, taken from the directory name.
|
|
125
|
+
*
|
|
126
|
+
* The location is the fact: `initRun` names the directory after the run
|
|
127
|
+
* it binds, so a `run.json` whose `id` disagrees with its own directory
|
|
128
|
+
* was moved or hand-edited, and the directory is the half that decides
|
|
129
|
+
* where the evidence actually is.
|
|
130
|
+
*/
|
|
131
|
+
readonly id: string
|
|
132
|
+
/** The parent run whose `children/` directory holds this one. */
|
|
133
|
+
readonly parentRunId: string
|
|
134
|
+
/** Absolute path to the child's evidence directory. */
|
|
135
|
+
readonly dir: string
|
|
136
|
+
readonly agentId?: string
|
|
137
|
+
readonly agentName?: string
|
|
138
|
+
/** `metadata.config.model` — the model the child was configured with. */
|
|
139
|
+
readonly model?: string
|
|
140
|
+
readonly status?: RunExecutionStatus
|
|
141
|
+
readonly startedAt?: number
|
|
142
|
+
readonly endedAt?: number
|
|
143
|
+
/** `tokenUsage.totalTokens` — this child's own cumulative spend. */
|
|
144
|
+
readonly totalTokens?: number
|
|
145
|
+
readonly depth?: number
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
/** Absence proves no recorded start only when the whole selected log is complete. */
|
|
107
149
|
export interface ToolExecutionSnapshot {
|
|
108
150
|
readonly complete: boolean
|