agent-relay-sdk 0.2.16 → 0.2.18
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/dist/bus-client.d.ts +5 -4
- package/dist/bus-client.d.ts.map +1 -1
- package/dist/claim-tracker.d.ts +1 -18
- package/dist/claim-tracker.d.ts.map +1 -1
- package/dist/claim-tracker.js +2 -1
- package/dist/claim-tracker.js.map +1 -1
- package/dist/context-probe.d.ts +4 -6
- package/dist/context-probe.d.ts.map +1 -1
- package/dist/context-probe.js +11 -5
- package/dist/context-probe.js.map +1 -1
- package/dist/fs-name.d.ts +2 -1
- package/dist/fs-name.d.ts.map +1 -1
- package/dist/fs-name.js +7 -4
- package/dist/fs-name.js.map +1 -1
- package/dist/http-client.d.ts +3 -10
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +1 -1
- package/dist/http-client.js.map +1 -1
- package/dist/process-utils.d.ts +0 -2
- package/dist/process-utils.d.ts.map +1 -1
- package/dist/process-utils.js +1 -1
- package/dist/process-utils.js.map +1 -1
- package/dist/protocol.d.ts +11 -17
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +7 -7
- package/dist/protocol.js.map +1 -1
- package/dist/provider-base.d.ts +3 -26
- package/dist/provider-base.d.ts.map +1 -1
- package/dist/provider-base.js +1 -1
- package/dist/provider-base.js.map +1 -1
- package/dist/provider-catalog.d.ts +10 -10
- package/dist/provider-catalog.d.ts.map +1 -1
- package/dist/provider-catalog.js +1 -1
- package/dist/provider-catalog.js.map +1 -1
- package/dist/reconnect.d.ts +2 -1
- package/dist/reconnect.d.ts.map +1 -1
- package/dist/speech-text.d.ts +10 -2
- package/dist/speech-text.d.ts.map +1 -1
- package/dist/speech-text.js +65 -1
- package/dist/speech-text.js.map +1 -1
- package/dist/sse.d.ts +2 -1
- package/dist/sse.d.ts.map +1 -1
- package/dist/types.d.ts +35 -46
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +24 -3
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/bus-client.ts +4 -4
- package/src/claim-tracker.ts +4 -4
- package/src/context-probe.ts +14 -8
- package/src/fs-name.ts +6 -3
- package/src/http-client.ts +3 -3
- package/src/process-utils.ts +1 -1
- package/src/protocol.ts +17 -17
- package/src/provider-base.ts +4 -4
- package/src/provider-catalog.ts +10 -10
- package/src/reconnect.ts +1 -1
- package/src/speech-text.ts +69 -2
- package/src/sse.ts +1 -1
- package/src/types.ts +54 -20
package/src/context-probe.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { ContextProbeMetrics } from "./types.js";
|
|
|
6
6
|
import { sanitizeFsName } from "./fs-name.js";
|
|
7
7
|
import { isRecord, stringValue } from "./types.js";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
interface ContextProbeOptions {
|
|
10
10
|
wrapCommand?: string;
|
|
11
11
|
agentId?: string;
|
|
12
12
|
stateDir?: string;
|
|
@@ -16,13 +16,13 @@ export interface ContextProbeOptions {
|
|
|
16
16
|
parsePatterns?: ParsePattern[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
interface ParsePattern {
|
|
20
20
|
name: string;
|
|
21
21
|
regex: RegExp;
|
|
22
22
|
extract(match: RegExpMatchArray): Partial<ContextProbeMetrics>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
interface ContextProbeRunResult {
|
|
26
26
|
metrics: ContextProbeMetrics | null;
|
|
27
27
|
stateFile?: string;
|
|
28
28
|
wrappedExitCode?: number;
|
|
@@ -31,9 +31,15 @@ export interface ContextProbeRunResult {
|
|
|
31
31
|
output: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// Lazy on purpose: calling tmpdir() at module top level made the whole SDK
|
|
35
|
+
// barrel browser-hostile — a bare-barrel import crashed the dashboard because
|
|
36
|
+
// node:os.tmpdir is undefined in the browser (#281). A function defers the
|
|
37
|
+
// node-only call to runtime, so importing this module never executes it.
|
|
38
|
+
function defaultContextProbeStateDir(): string {
|
|
39
|
+
return tmpdir();
|
|
40
|
+
}
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
const DEFAULT_CONTEXT_PROBE_PATTERNS: ParsePattern[] = [
|
|
37
43
|
{
|
|
38
44
|
name: "percent",
|
|
39
45
|
regex: /(\d+(?:\.\d+)?)%\s*(?:context|ctx)?/i,
|
|
@@ -54,7 +60,7 @@ export const DEFAULT_CONTEXT_PROBE_PATTERNS: ParsePattern[] = [
|
|
|
54
60
|
},
|
|
55
61
|
];
|
|
56
62
|
|
|
57
|
-
export function contextProbeStatePath(agentId: string, stateDir =
|
|
63
|
+
export function contextProbeStatePath(agentId: string, stateDir = defaultContextProbeStateDir()): string {
|
|
58
64
|
return join(stateDir, `agent-relay-context-${safeStateId(agentId)}.json`);
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -104,7 +110,7 @@ export function contextStateFromProbeMetrics(metrics: ContextProbeMetrics) {
|
|
|
104
110
|
};
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
function writeContextProbeState(metrics: ContextProbeMetrics, stateDir = defaultContextProbeStateDir()): string {
|
|
108
114
|
const file = contextProbeStatePath(metrics.agentId, stateDir);
|
|
109
115
|
mkdirSync(dirname(file), { recursive: true });
|
|
110
116
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
@@ -113,7 +119,7 @@ export function writeContextProbeState(metrics: ContextProbeMetrics, stateDir =
|
|
|
113
119
|
return file;
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
export function readContextProbeState(agentId: string, stateDir =
|
|
122
|
+
export function readContextProbeState(agentId: string, stateDir = defaultContextProbeStateDir()): ContextProbeMetrics | null {
|
|
117
123
|
try {
|
|
118
124
|
const parsed = JSON.parse(readFileSync(contextProbeStatePath(agentId, stateDir), "utf8")) as unknown;
|
|
119
125
|
return isContextProbeMetrics(parsed) ? parsed : null;
|
package/src/fs-name.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Allowed characters (everything else is replaced): a-z A-Z 0-9 . _ -
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface SanitizeFsNameOptions {
|
|
11
11
|
/** Truncate the result to this many chars. Omit for no truncation. */
|
|
12
12
|
maxLen?: number;
|
|
13
13
|
/** Character substituted for disallowed input. Default "_". */
|
|
@@ -37,11 +37,14 @@ export function sanitizeFsName(value: string, opts: SanitizeFsNameOptions = {}):
|
|
|
37
37
|
if (opts.collapseReplacement) {
|
|
38
38
|
s = s.replace(new RegExp(`${escapeRe(repl)}+`, "g"), repl);
|
|
39
39
|
}
|
|
40
|
+
if (opts.lowercase) s = s.toLowerCase();
|
|
41
|
+
if (opts.maxLen !== undefined) s = s.slice(0, opts.maxLen);
|
|
42
|
+
// Edge-trim AFTER truncating: a maxLen cut that lands on a replacement char
|
|
43
|
+
// (e.g. a UUID sliced mid-segment) would otherwise leave a dangling separator
|
|
44
|
+
// — the "branch ref severed at `…b7e1-`" bug (#282).
|
|
40
45
|
if (opts.trimEdge) {
|
|
41
46
|
const e = escapeRe(repl);
|
|
42
47
|
s = s.replace(new RegExp(`^(?:${e})+|(?:${e})+$`, "g"), "");
|
|
43
48
|
}
|
|
44
|
-
if (opts.lowercase) s = s.toLowerCase();
|
|
45
|
-
if (opts.maxLen !== undefined) s = s.slice(0, opts.maxLen);
|
|
46
49
|
return s || opts.fallback || "";
|
|
47
50
|
}
|
package/src/http-client.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { AgentCard, Artifact, ArtifactKind, ArtifactSensitivity, PollQuery, RegisterAgentInput, SendMessageInput, Message, MessageDeliveryState, MessageDeliveryStatus, ReplyObligation, Task, TaskStatusInput } from "./types.js";
|
|
2
2
|
import { RELAY_TOKEN_HEADER } from "./contracts.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interface HttpClientOptions {
|
|
5
5
|
baseUrl: string;
|
|
6
6
|
token?: string;
|
|
7
7
|
timeout?: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface TransportOptions {
|
|
11
11
|
/** External abort signal, combined (AbortSignal.any) with the internal timeout. */
|
|
12
12
|
signal?: AbortSignal;
|
|
13
13
|
/** Override the default request timeout (ms). `null` disables it — for streaming (SSE). */
|
|
14
14
|
timeoutMs?: number | null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
class RelayHttpError extends Error {
|
|
18
18
|
constructor(
|
|
19
19
|
readonly method: string,
|
|
20
20
|
readonly path: string,
|
package/src/process-utils.ts
CHANGED
|
@@ -15,7 +15,7 @@ export function parseProcStateIsZombie(statusText: string): boolean {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** True iff <pid> exists and its /proc state is zombie. Non-Linux → false. */
|
|
18
|
-
|
|
18
|
+
function isZombie(pid: number): boolean {
|
|
19
19
|
try {
|
|
20
20
|
return parseProcStateIsZombie(readFileSync(`/proc/${pid}/status`, "utf8"));
|
|
21
21
|
} catch {
|
package/src/protocol.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface RegisterFrame extends BusFrame {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
interface HeartbeatFrame extends BusFrame {
|
|
28
28
|
type: "heartbeat";
|
|
29
29
|
payload: {
|
|
30
30
|
status: Exclude<BusAgentStatus, "offline">;
|
|
@@ -32,7 +32,7 @@ export interface HeartbeatFrame extends BusFrame {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
interface CommandFrame extends BusFrame {
|
|
36
36
|
type: "command";
|
|
37
37
|
id: string;
|
|
38
38
|
payload: {
|
|
@@ -42,7 +42,7 @@ export interface CommandFrame extends BusFrame {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
interface SubscribeFrame extends BusFrame {
|
|
46
46
|
type: "subscribe";
|
|
47
47
|
id: string;
|
|
48
48
|
payload: {
|
|
@@ -51,7 +51,7 @@ export interface SubscribeFrame extends BusFrame {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
interface StatusFrame extends BusFrame {
|
|
55
55
|
type: "status";
|
|
56
56
|
payload: {
|
|
57
57
|
agentStatus: BusAgentStatus;
|
|
@@ -60,14 +60,14 @@ export interface StatusFrame extends BusFrame {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
interface AckFrame extends BusFrame {
|
|
64
64
|
type: "ack";
|
|
65
65
|
payload: {
|
|
66
66
|
frameId: string;
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
interface ResumeFrame extends BusFrame {
|
|
71
71
|
type: "resume";
|
|
72
72
|
id: string;
|
|
73
73
|
payload: {
|
|
@@ -87,7 +87,7 @@ export interface RegisteredFrame extends BusFrame {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
interface EventPayload {
|
|
91
91
|
seq: number;
|
|
92
92
|
eventType: string;
|
|
93
93
|
source: string;
|
|
@@ -102,7 +102,7 @@ export interface EventFrame extends BusFrame {
|
|
|
102
102
|
payload: EventPayload;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
interface CommandResultFrame extends BusFrame {
|
|
106
106
|
type: "command.result";
|
|
107
107
|
payload: {
|
|
108
108
|
commandId: string;
|
|
@@ -118,7 +118,7 @@ export interface CommandStatusUpdateParams {
|
|
|
118
118
|
error?: string;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
interface ResumedFrame extends BusFrame {
|
|
122
122
|
type: "resumed";
|
|
123
123
|
payload: {
|
|
124
124
|
events: EventPayload[];
|
|
@@ -136,7 +136,7 @@ export interface ErrorFrame extends BusFrame {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
type ClientBusFrame =
|
|
140
140
|
| RegisterFrame
|
|
141
141
|
| HeartbeatFrame
|
|
142
142
|
| CommandFrame
|
|
@@ -162,13 +162,13 @@ export class BusProtocolError extends Error {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
165
|
+
function isRegisterFrame(f: BusFrame): f is RegisterFrame { return f.type === "register"; }
|
|
166
|
+
function isHeartbeatFrame(f: BusFrame): f is HeartbeatFrame { return f.type === "heartbeat"; }
|
|
167
|
+
function isCommandFrame(f: BusFrame): f is CommandFrame { return f.type === "command"; }
|
|
168
|
+
function isSubscribeFrame(f: BusFrame): f is SubscribeFrame { return f.type === "subscribe"; }
|
|
169
|
+
function isStatusFrame(f: BusFrame): f is StatusFrame { return f.type === "status"; }
|
|
170
|
+
function isAckFrame(f: BusFrame): f is AckFrame { return f.type === "ack"; }
|
|
171
|
+
function isResumeFrame(f: BusFrame): f is ResumeFrame { return f.type === "resume"; }
|
|
172
172
|
|
|
173
173
|
export function parseBusFrame(data: string | ArrayBuffer | Uint8Array): BusFrame {
|
|
174
174
|
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
package/src/provider-base.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface ProviderAdapter {
|
|
|
11
11
|
formatDelivery?(messages: Message[]): string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
interface SpawnConfig {
|
|
15
15
|
cwd: string;
|
|
16
16
|
label?: string;
|
|
17
17
|
approvalMode?: string;
|
|
@@ -19,13 +19,13 @@ export interface SpawnConfig {
|
|
|
19
19
|
env?: Record<string, string>;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
interface SpawnResult {
|
|
23
23
|
agentId: string;
|
|
24
24
|
pid?: number;
|
|
25
25
|
sessionId?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
interface ProviderBaseOptions {
|
|
29
29
|
relayUrl: string;
|
|
30
30
|
adapter: ProviderAdapter;
|
|
31
31
|
agentId: string;
|
|
@@ -36,7 +36,7 @@ export interface ProviderBaseOptions {
|
|
|
36
36
|
token?: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
class ProviderBase {
|
|
40
40
|
readonly client: RelayBusClient;
|
|
41
41
|
private running = false;
|
|
42
42
|
private readonly pending = new Map<number, Message>();
|
package/src/provider-catalog.ts
CHANGED
|
@@ -3,25 +3,25 @@ import type { SpawnProvider } from "./types.js";
|
|
|
3
3
|
/** Valid reasoning-effort levels — runtime tuple is the single source of truth. */
|
|
4
4
|
export const VALID_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;
|
|
5
5
|
export type ProviderEffort = (typeof VALID_EFFORTS)[number];
|
|
6
|
-
|
|
6
|
+
function isProviderEffort(value: unknown): value is ProviderEffort {
|
|
7
7
|
return typeof value === "string" && (VALID_EFFORTS as readonly string[]).includes(value);
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
type ProviderCatalogValueSource = "catalog" | "provider" | "runtime" | "override";
|
|
10
|
+
type ProviderCatalogConfidence = "declared" | "verified" | "estimated" | "unknown";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
interface ProviderCatalogValue<T> {
|
|
13
13
|
value: T;
|
|
14
14
|
source: ProviderCatalogValueSource;
|
|
15
15
|
confidence: ProviderCatalogConfidence;
|
|
16
16
|
lastUpdatedAt?: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
interface ProviderModelLimits {
|
|
20
20
|
contextWindowTokens?: ProviderCatalogValue<number>;
|
|
21
21
|
maxOutputTokens?: ProviderCatalogValue<number>;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
interface ProviderModelModalities {
|
|
25
25
|
input: {
|
|
26
26
|
text: boolean;
|
|
27
27
|
image?: boolean;
|
|
@@ -37,7 +37,7 @@ export interface ProviderModelModalities {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
interface ProviderModelToolCapabilities {
|
|
41
41
|
code?: boolean;
|
|
42
42
|
review?: boolean;
|
|
43
43
|
debug?: boolean;
|
|
@@ -50,7 +50,7 @@ export interface ProviderModelToolCapabilities {
|
|
|
50
50
|
imageEditing?: boolean;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
interface ProviderModelCapabilities {
|
|
54
54
|
modalities: ProviderModelModalities;
|
|
55
55
|
tools?: ProviderModelToolCapabilities;
|
|
56
56
|
source: ProviderCatalogValueSource;
|
|
@@ -75,13 +75,13 @@ export interface ProviderCatalogEntry {
|
|
|
75
75
|
models: ProviderModelCatalogEntry[];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
interface ProviderSelection {
|
|
79
79
|
provider: SpawnProvider;
|
|
80
80
|
model?: string;
|
|
81
81
|
effort?: ProviderEffort;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
interface ResolvedProviderSelection {
|
|
85
85
|
provider: SpawnProvider;
|
|
86
86
|
modelAlias?: string;
|
|
87
87
|
providerModel?: string;
|
package/src/reconnect.ts
CHANGED
package/src/speech-text.ts
CHANGED
|
@@ -111,7 +111,7 @@ function collapseTables(text: string): string {
|
|
|
111
111
|
* by appending an entry. Order matters: earlier rules see the raw text, later
|
|
112
112
|
* rules see the output of earlier ones (see the ordering notes in SPEECH_RULES).
|
|
113
113
|
*/
|
|
114
|
-
|
|
114
|
+
interface SpeechRule {
|
|
115
115
|
readonly name: string
|
|
116
116
|
readonly pattern: RegExp
|
|
117
117
|
readonly replace: string | ((substring: string, ...args: string[]) => string)
|
|
@@ -131,13 +131,33 @@ function spaceDigits(frac: string): string {
|
|
|
131
131
|
return frac.split('').join(' ')
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Spell a token one character at a time so the engine reads each symbol on its
|
|
136
|
+
* own: digits stay as digits, letters are upper-cased so they're heard as the
|
|
137
|
+
* letter name ("e" → "E" = "ee") rather than a syllable. Used for commit hashes
|
|
138
|
+
* ("834543e" → "8 3 4 5 4 3 E") and acronyms ("id" → "I D").
|
|
139
|
+
*/
|
|
140
|
+
function spellOut(token: string): string {
|
|
141
|
+
return token
|
|
142
|
+
.split('')
|
|
143
|
+
.map((c) => (/[0-9]/.test(c) ? c : c.toUpperCase()))
|
|
144
|
+
.join(' ')
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
/**
|
|
135
148
|
* Ordered normalization rules. ORDER IS LOAD-BEARING:
|
|
136
149
|
* - URLs/paths first, before anything mangles their slashes/dots.
|
|
137
150
|
* - `~` → "approximately" before number rules consume the digits after it.
|
|
138
151
|
* - number ranges ("5-7") before unit expansion, so the unit attaches once: "5 to 7 seconds".
|
|
152
|
+
* - versions ("0.27.0") before decimals, so all dots become "dot" uniformly
|
|
153
|
+
* instead of the decimal rule eating only the first pair and stranding ".0".
|
|
139
154
|
* - decimals before unit expansion and before the sentence splitter sees the dot.
|
|
140
|
-
* -
|
|
155
|
+
* - commit-hash spelling before nothing in particular, but after decimals so a
|
|
156
|
+
* real number is never mistaken for a hash.
|
|
157
|
+
* - "id" acronym expansion (camelCase suffix before the standalone word).
|
|
158
|
+
* - unit expansion among the number rules.
|
|
159
|
+
* - interior-dot LAST, so it only mops up dots the structured rules left behind
|
|
160
|
+
* (identifiers like "branch.landed", "router.ts") — never a decimal or version.
|
|
141
161
|
*/
|
|
142
162
|
export const SPEECH_RULES: readonly SpeechRule[] = [
|
|
143
163
|
// Bare URLs read character-by-character → say "link".
|
|
@@ -148,14 +168,46 @@ export const SPEECH_RULES: readonly SpeechRule[] = [
|
|
|
148
168
|
pattern: /(?:\/[A-Za-z0-9._-]+){2,}\/?/g,
|
|
149
169
|
replace: (m) => ' ' + m.replace(/[/._-]+/g, ' ').trim() + ' ',
|
|
150
170
|
},
|
|
171
|
+
// A lone slash between words ("src/router.ts", "TCP/IP") → a space, so the
|
|
172
|
+
// engine doesn't read "slash". Absolute multi-segment paths are already gone.
|
|
173
|
+
{ name: 'slash', pattern: /(?<=[A-Za-z0-9])\/(?=[A-Za-z0-9])/g, replace: ' ' },
|
|
151
174
|
// Empty-paren code identifiers ("fetchSpeech()") → drop the parens.
|
|
152
175
|
{ name: 'func-call', pattern: /\b([A-Za-z_$][\w$]*)\(\)/g, replace: '$1' },
|
|
153
176
|
// Approximation tilde ("~0.1", "~5") → "approximately".
|
|
154
177
|
{ name: 'approx', pattern: /~(?=\s*[\d.])/g, replace: 'approximately ' },
|
|
155
178
|
// Numeric ranges ("5-7", "100–200") → "X to Y".
|
|
156
179
|
{ name: 'range', pattern: /(\d)\s*[-–—]\s*(?=\d)/g, replace: '$1 to ' },
|
|
180
|
+
// Dotted versions ("0.27.0", "v1.2.3", IPs) → every dot spoken as "dot".
|
|
181
|
+
// Runs BEFORE decimal so 3+-part versions don't get half-eaten into "point".
|
|
182
|
+
// A leading v/V is voiced as "version" (unambiguous here — v then digits then ≥2 dots).
|
|
183
|
+
{
|
|
184
|
+
name: 'version',
|
|
185
|
+
pattern: /\b(v?)(\d+(?:\.\d+){2,})\b/gi,
|
|
186
|
+
replace: (_m, v: string, nums: string) =>
|
|
187
|
+
`${v ? 'version ' : ''}${nums.replace(/\./g, ' dot ')}`,
|
|
188
|
+
},
|
|
157
189
|
// Decimals ("0.1", "3.14") → "0 point 1" so the sentence splitter keeps them whole.
|
|
158
190
|
{ name: 'decimal', pattern: /\b(\d+)\.(\d+)\b/g, replace: (_m, i: string, f: string) => `${i} point ${spaceDigits(f)}` },
|
|
191
|
+
// Commit hashes ("834543e", "3e0c66f85bd5") → spell each char so the engine
|
|
192
|
+
// doesn't read them as one giant number. Heuristic: 7–40 hex chars containing
|
|
193
|
+
// BOTH a letter and a digit — excludes plain numbers and plain words.
|
|
194
|
+
{
|
|
195
|
+
name: 'hash',
|
|
196
|
+
pattern: /\b(?=[0-9a-f]{7,40}\b)(?=[0-9a-f]*[a-f])(?=[0-9a-f]*\d)[0-9a-f]{7,40}\b/gi,
|
|
197
|
+
replace: (m: string) => spellOut(m),
|
|
198
|
+
},
|
|
199
|
+
// camelCase "Id" suffix ("messageId", "threadIds") → "message I D", "thread I Ds".
|
|
200
|
+
{
|
|
201
|
+
name: 'id-camel',
|
|
202
|
+
pattern: /([a-z])(Id|ID)(s)?\b/g,
|
|
203
|
+
replace: (_m, pre: string, _acr: string, plural: string) => `${pre} I D${plural ? 's' : ''}`,
|
|
204
|
+
},
|
|
205
|
+
// Standalone "id"/"Id"/"ID" word → "I D" (so it's not read as "iiid").
|
|
206
|
+
{
|
|
207
|
+
name: 'id-word',
|
|
208
|
+
pattern: /\b(?:id|Id|ID)(s)?\b/g,
|
|
209
|
+
replace: (_m, plural: string) => `I D${plural ? 's' : ''}`,
|
|
210
|
+
},
|
|
159
211
|
// Byte units ("165KB", "2 MB") → spelled out (case-insensitive).
|
|
160
212
|
{
|
|
161
213
|
name: 'byte-unit',
|
|
@@ -165,6 +217,21 @@ export const SPEECH_RULES: readonly SpeechRule[] = [
|
|
|
165
217
|
// Time units ("500ms", "5s") → spelled out. Guard against 4-digit years for "s".
|
|
166
218
|
{ name: 'ms-unit', pattern: /\b(\d{1,5})\s?ms\b/g, replace: (_m, n: string) => `${n} ${UNIT_WORDS.ms}` },
|
|
167
219
|
{ name: 's-unit', pattern: /\b(\d{1,3})\s?s\b/g, replace: (_m, n: string) => `${n} ${UNIT_WORDS.s}` },
|
|
220
|
+
// Heteronym "live": the engine defaults to the verb /lɪv/ ("I want to live"),
|
|
221
|
+
// but in deployment chatter it's the adjective /laɪv/ ("the version is live").
|
|
222
|
+
// Respell → "lyve" ONLY after a high-confidence adjective cue, so the verb is
|
|
223
|
+
// never touched. NOTE: heteronym sense can't be fully disambiguated by regex —
|
|
224
|
+
// this deliberately covers only the predicate/"go live" phrasings agents use.
|
|
225
|
+
{
|
|
226
|
+
name: 'live-adjective',
|
|
227
|
+
pattern:
|
|
228
|
+
/\b(is|are|was|were|be|been|being|now|and|stays?|staying|went|go|goes|going|deployed|it's|that's|here's|there's|we're|you're|they're)\s+live\b/gi,
|
|
229
|
+
replace: (_m, cue: string) => `${cue} lyve`,
|
|
230
|
+
},
|
|
231
|
+
// Interior dot — any dot glued directly to a following alphanumeric ("branch.landed",
|
|
232
|
+
// "router.ts", "Node.js") → " dot ". Runs LAST: real sentence-ending dots (followed by
|
|
233
|
+
// a space or end-of-string) are untouched, and versions/decimals were already consumed.
|
|
234
|
+
{ name: 'interior-dot', pattern: /\.(?=[A-Za-z0-9])/g, replace: ' dot ' },
|
|
168
235
|
]
|
|
169
236
|
|
|
170
237
|
/**
|
package/src/sse.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// (voice connector). One home for the `event:`/`data:`/`retry:` field parse
|
|
4
4
|
// that was hand-rolled in connectors/voice and dashboard/src/lib/api.ts.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
interface ParsedSseFrame {
|
|
7
7
|
/** Event name; defaults to "message" per the SSE spec when no `event:` field. */
|
|
8
8
|
event: string;
|
|
9
9
|
/** Each `data:` line, in order. Join with "\n" to reconstruct the payload. */
|
package/src/types.ts
CHANGED
|
@@ -26,8 +26,8 @@ export interface AgentCard {
|
|
|
26
26
|
|
|
27
27
|
export type AgentStatus = "online" | "idle" | "busy" | "stale" | "offline";
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
type CapabilitySource = "catalog" | "provider" | "runtime" | "override" | "estimate";
|
|
30
|
+
type CapabilityConfidence = "declared" | "reported" | "verified" | "estimated" | "unknown";
|
|
31
31
|
|
|
32
32
|
export interface ProviderCapabilities {
|
|
33
33
|
lifecycle: {
|
|
@@ -99,7 +99,7 @@ export interface ProviderCapabilities {
|
|
|
99
99
|
lastUpdatedAt: number;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
type ContextLifecycleState =
|
|
103
103
|
| "fresh"
|
|
104
104
|
| "primed"
|
|
105
105
|
| "working"
|
|
@@ -249,7 +249,7 @@ export interface ContextBudget {
|
|
|
249
249
|
priorityCutoff: 1 | 2 | 3;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
interface TaskHistorySummary {
|
|
253
253
|
taskId: number;
|
|
254
254
|
agentId: string;
|
|
255
255
|
status: string;
|
|
@@ -303,7 +303,7 @@ export interface MemoryBrokerContext {
|
|
|
303
303
|
|
|
304
304
|
export type MemoryBrokerKind = "sqlite" | "http" | "command";
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
interface SqliteMemoryBrokerConfig {
|
|
307
307
|
type: "sqlite";
|
|
308
308
|
}
|
|
309
309
|
|
|
@@ -332,17 +332,44 @@ export type MessageKind =
|
|
|
332
332
|
| "system"
|
|
333
333
|
| "session";
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Mechanical message kinds: the relay's own lifecycle/observability lane, not
|
|
337
|
+
* agent-directed messaging. They bypass delivery resolution (never re-delivered
|
|
338
|
+
* into a session) and — when targeting a reserved sink — the recipient-constraint
|
|
339
|
+
* auth check (a managed token's `targets`/`policies`/`agents` constraints gate which
|
|
340
|
+
* *agents* it may message, not a session-mirror capture to the reserved sink; #284).
|
|
341
|
+
* Keep in sync with the `MessageKind` union.
|
|
342
|
+
*/
|
|
343
|
+
export const MECHANICAL_MESSAGE_KINDS: readonly MessageKind[] = ["system", "control", "session"];
|
|
344
|
+
|
|
345
|
+
export function isMechanicalMessageKind(kind: string | undefined): boolean {
|
|
346
|
+
return MECHANICAL_MESSAGE_KINDS.includes((kind ?? "") as MessageKind);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Reserved built-in identities that are not real agents: the `user` chat/mirror sink
|
|
351
|
+
* and the `system` lifecycle sender. They are never registered, polled, or delivered to
|
|
352
|
+
* like agents, so recipient constraints don't apply to mechanical posts addressed to them.
|
|
353
|
+
*/
|
|
354
|
+
export const RESERVED_AGENT_IDS: readonly string[] = ["user", "system"];
|
|
355
|
+
|
|
356
|
+
export function isReservedAgentId(id: string | undefined): boolean {
|
|
357
|
+
return id === "user" || id === "system";
|
|
358
|
+
}
|
|
359
|
+
|
|
335
360
|
/**
|
|
336
361
|
* Session-mirror event taxonomy. Every `kind: "session"` message carries a
|
|
337
362
|
* `payload.session` of this shape so the dashboard can render the live provider
|
|
338
363
|
* session faithfully regardless of which surface (chat box or web terminal)
|
|
339
|
-
* started the turn. `prompt`/`response` render as chat bubbles; `
|
|
340
|
-
*
|
|
364
|
+
* started the turn. `prompt`/`response` render as chat bubbles; `narration` is
|
|
365
|
+
* the agent's intermediate spoken text between tool calls (the terminal's `●`
|
|
366
|
+
* lines) and renders inline in the turn's activity trace; `reasoning`, `tool`,
|
|
367
|
+
* and `notice` render discreetly (collapsed/inline activity, never bubbles).
|
|
341
368
|
* A legacy session message with no `payload.session` is treated as a `response`.
|
|
342
369
|
*/
|
|
343
|
-
|
|
370
|
+
type SessionEventType = "prompt" | "response" | "narration" | "reasoning" | "tool" | "notice";
|
|
344
371
|
|
|
345
|
-
|
|
372
|
+
type SessionEventOrigin = "chat" | "terminal" | "provider";
|
|
346
373
|
|
|
347
374
|
export interface MessageSessionMeta {
|
|
348
375
|
type: SessionEventType;
|
|
@@ -435,6 +462,10 @@ export interface Message {
|
|
|
435
462
|
body: string;
|
|
436
463
|
threadId?: number;
|
|
437
464
|
replyTo?: number;
|
|
465
|
+
// Server-owned reply obligation (#283). Absent/true = the message wants a response (default);
|
|
466
|
+
// false = a notification (FYI, merge notice, lifecycle event) that must NOT be replied to.
|
|
467
|
+
// The server keys footer rendering and the reply-obligation tracker off this, not off `from`.
|
|
468
|
+
replyExpected?: boolean;
|
|
438
469
|
claimable?: boolean;
|
|
439
470
|
claimedBy?: string;
|
|
440
471
|
claimedAt?: number;
|
|
@@ -538,6 +569,9 @@ export interface SendMessageInput {
|
|
|
538
569
|
subject?: string;
|
|
539
570
|
body: string;
|
|
540
571
|
replyTo?: number;
|
|
572
|
+
// Defaults to true server-side. Set false to mark a notification (no reply wanted) — the
|
|
573
|
+
// server suppresses the reply-scaffold footer and appends a one-line no-reply nudge (#283).
|
|
574
|
+
replyExpected?: boolean;
|
|
541
575
|
claimable?: boolean;
|
|
542
576
|
idempotencyKey?: string;
|
|
543
577
|
maxAgeSeconds?: number;
|
|
@@ -1141,14 +1175,14 @@ export type OrchestratorStatus = "online" | "offline";
|
|
|
1141
1175
|
/** Spawn providers — the runtime tuple is the single source of truth; the type derives from it. */
|
|
1142
1176
|
export const SPAWN_PROVIDERS = ["claude", "codex"] as const;
|
|
1143
1177
|
export type SpawnProvider = (typeof SPAWN_PROVIDERS)[number];
|
|
1144
|
-
|
|
1178
|
+
function isSpawnProvider(value: unknown): value is SpawnProvider {
|
|
1145
1179
|
return typeof value === "string" && (SPAWN_PROVIDERS as readonly string[]).includes(value);
|
|
1146
1180
|
}
|
|
1147
1181
|
|
|
1148
1182
|
/** Approval modes — runtime tuple + derived type. */
|
|
1149
1183
|
export const APPROVAL_MODES = ["open", "guarded", "read-only"] as const;
|
|
1150
1184
|
export type SpawnApprovalMode = (typeof APPROVAL_MODES)[number];
|
|
1151
|
-
|
|
1185
|
+
function isApprovalMode(value: unknown): value is SpawnApprovalMode {
|
|
1152
1186
|
return typeof value === "string" && (APPROVAL_MODES as readonly string[]).includes(value);
|
|
1153
1187
|
}
|
|
1154
1188
|
|
|
@@ -1194,7 +1228,7 @@ export interface WorkspaceProbe {
|
|
|
1194
1228
|
error?: string;
|
|
1195
1229
|
}
|
|
1196
1230
|
|
|
1197
|
-
|
|
1231
|
+
interface WorkspaceGitCommit {
|
|
1198
1232
|
sha: string;
|
|
1199
1233
|
message: string;
|
|
1200
1234
|
at?: number;
|
|
@@ -1539,7 +1573,7 @@ export interface ManagedAgentState {
|
|
|
1539
1573
|
updatedAt: number;
|
|
1540
1574
|
}
|
|
1541
1575
|
|
|
1542
|
-
|
|
1576
|
+
type SpawnPolicyMode = "always-on" | "on-demand";
|
|
1543
1577
|
|
|
1544
1578
|
export type AgentProfileProvider = SpawnProvider | "any";
|
|
1545
1579
|
export type AgentProfileBase = "host" | "minimal" | "isolated";
|
|
@@ -1912,7 +1946,7 @@ export interface OrchestratorRuntimeInput {
|
|
|
1912
1946
|
providerCatalog?: ProviderCatalogSummary[];
|
|
1913
1947
|
}
|
|
1914
1948
|
|
|
1915
|
-
|
|
1949
|
+
interface OrchestratorSpawnInput {
|
|
1916
1950
|
provider: SpawnProvider;
|
|
1917
1951
|
model?: string;
|
|
1918
1952
|
effort?: SpawnEffort;
|
|
@@ -2007,7 +2041,7 @@ export interface ProviderCatalogSummary {
|
|
|
2007
2041
|
}>;
|
|
2008
2042
|
}
|
|
2009
2043
|
|
|
2010
|
-
|
|
2044
|
+
interface OrchestratorSpawnResult {
|
|
2011
2045
|
orchestratorId: string;
|
|
2012
2046
|
provider: SpawnProvider;
|
|
2013
2047
|
sessionName?: string;
|
|
@@ -2048,19 +2082,19 @@ export interface RecipeAgent {
|
|
|
2048
2082
|
env?: Record<string, string>;
|
|
2049
2083
|
}
|
|
2050
2084
|
|
|
2051
|
-
|
|
2085
|
+
interface RecipeWorkflow {
|
|
2052
2086
|
trigger?: string;
|
|
2053
2087
|
fanOut?: "all" | "first";
|
|
2054
2088
|
collect?: string;
|
|
2055
2089
|
routing?: RecipeRoute[];
|
|
2056
2090
|
}
|
|
2057
2091
|
|
|
2058
|
-
|
|
2092
|
+
interface RecipeRoute {
|
|
2059
2093
|
pattern: string;
|
|
2060
2094
|
pipeline: string[];
|
|
2061
2095
|
}
|
|
2062
2096
|
|
|
2063
|
-
|
|
2097
|
+
interface RecipeLifecycle {
|
|
2064
2098
|
mode?: "persistent" | "ephemeral";
|
|
2065
2099
|
idleTimeoutMs?: number;
|
|
2066
2100
|
memory?: RecipeMemoryPolicy;
|
|
@@ -2177,7 +2211,7 @@ export interface TokenConstraints {
|
|
|
2177
2211
|
canDelegate?: boolean;
|
|
2178
2212
|
}
|
|
2179
2213
|
|
|
2180
|
-
|
|
2214
|
+
type TokenScope =
|
|
2181
2215
|
| "system:admin"
|
|
2182
2216
|
| "token:read"
|
|
2183
2217
|
| "token:write"
|
|
@@ -2282,7 +2316,7 @@ export function errMessage(error: unknown): string {
|
|
|
2282
2316
|
// --- Relay connection defaults ---
|
|
2283
2317
|
|
|
2284
2318
|
/** Default port the relay server listens on. */
|
|
2285
|
-
|
|
2319
|
+
const DEFAULT_RELAY_PORT = 4850;
|
|
2286
2320
|
|
|
2287
2321
|
/** Default relay base URL. Loopback spelling settled on `127.0.0.1` (not `localhost`). */
|
|
2288
2322
|
export const DEFAULT_RELAY_URL = `http://127.0.0.1:${DEFAULT_RELAY_PORT}`;
|