@sema-agent/server 7.126.0 → 7.127.1
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/MIGRATION.md +38 -0
- package/USAGE.md +86 -8
- package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +15 -5
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +28 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +16 -6
- package/deploy/sema-up/kube-up.sh +10 -4
- package/deploy/sema-up/lib/node-addrs.sh +42 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +20 -1
- package/dist/audit.js +1 -0
- package/dist/boot/execution-env.js +17 -4
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/memory-consolidation.d.ts +37 -3
- package/dist/boot/memory-consolidation.js +18 -2
- package/dist/boot/runner-deps.d.ts +2 -1
- package/dist/boot/session-faces.d.ts +2 -1
- package/dist/boot/stage-06-runners.js +5 -2
- package/dist/config-catalog.js +3 -1
- package/dist/config-types.d.ts +5 -1
- package/dist/config.js +2 -0
- package/dist/execution-lane-caps.d.ts +12 -0
- package/dist/execution-lane-caps.js +16 -2
- package/dist/fleet/subagent-tail-bus.js +15 -5
- package/dist/http/admission.js +5 -1
- package/dist/http/durable-run-leg.js +1 -3
- package/dist/http/resume-legs.js +15 -6
- package/dist/http/route-ctx.d.ts +7 -1
- package/dist/http/routes/runs.js +23 -1
- package/dist/http/routes/tasks.js +17 -8
- package/dist/http/routes/workflows.js +8 -0
- package/dist/http/server.js +1 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lsp/manager.js +1 -1
- package/dist/observability/fail-open.d.ts +16 -0
- package/dist/observability/fail-open.js +16 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +3 -0
- package/dist/plugins/caching-session-store.d.ts +39 -22
- package/dist/plugins/caching-session-store.js +16 -9
- package/dist/plugins/host-platform.d.ts +18 -3
- package/dist/plugins/host-platform.js +23 -10
- package/dist/plugins/k8s-exec-protocol.js +3 -0
- package/dist/plugins/one-time-migrations.d.ts +28 -4
- package/dist/plugins/one-time-migrations.js +22 -4
- package/dist/plugins/remote-env-host.d.ts +23 -6
- package/dist/plugins/remote-env-host.js +21 -12
- package/dist/plugins/remote-env-k8s.d.ts +40 -5
- package/dist/plugins/remote-env-k8s.js +119 -25
- package/dist/plugins/session-store.d.ts +4 -3
- package/dist/plugins/session-store.js +1 -1
- package/dist/plugins/store-backend.d.ts +12 -5
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +3 -0
- package/dist/plugins/store-open-wait.js +3 -0
- package/dist/request-key-closure.d.ts +20 -7
- package/dist/request-key-closure.js +22 -6
- package/dist/run-acceptance.d.ts +51 -0
- package/dist/run-acceptance.js +27 -0
- package/dist/runs.d.ts +7 -11
- package/dist/runs.js +5 -16
- package/dist/sandbox-egress-policy.d.ts +61 -0
- package/dist/sandbox-egress-policy.js +235 -0
- package/dist/session-error-wire.d.ts +2 -2
- package/dist/session-titler.d.ts +5 -3
- package/dist/trace/core-keyset-guard.d.ts +3 -2
- package/dist/trace/ledger-events.d.ts +9 -3
- package/dist/trace/ledger-sink.d.ts +7 -0
- package/dist/trace/ledger-sink.js +14 -4
- package/dist/trace/project.d.ts +49 -8
- package/dist/trace/project.js +39 -7
- package/dist/trace/self-minted-wire.d.ts +19 -29
- package/dist/trace/self-minted-wire.js +2 -9
- package/dist/trace/tool-output-media.d.ts +26 -0
- package/dist/trace/tool-output-media.js +57 -0
- package/dist/trace/wire-projection-faces.d.ts +7 -1
- package/dist/trace/wire-projection-faces.js +2 -1
- package/package.json +3 -3
package/dist/runs.js
CHANGED
|
@@ -64,13 +64,6 @@ export function markChildrenStoppedByUserOnAbort(signal, taskId, principal) {
|
|
|
64
64
|
}
|
|
65
65
|
signal.addEventListener("abort", mark, { once: true });
|
|
66
66
|
}
|
|
67
|
-
export function isSessionConflictResult(result) {
|
|
68
|
-
if (result.terminal.kind !== "failed")
|
|
69
|
-
return false;
|
|
70
|
-
if (result.terminal.code)
|
|
71
|
-
return result.terminal.code === "conflict";
|
|
72
|
-
return /stale write|concurrent write|conflict/i.test(result.terminal.message ?? "");
|
|
73
|
-
}
|
|
74
67
|
const PREPARE_REFUSAL_HTTP_STATUS = {
|
|
75
68
|
"resume.session_not_found": 404,
|
|
76
69
|
"rewind_snapshot.unresolvable": 422,
|
|
@@ -170,12 +163,7 @@ export async function graftParkToolCallId(result, cs) {
|
|
|
170
163
|
const id = await parkToolCallId(result, cs);
|
|
171
164
|
return id === undefined ? result : { ...result, toolCallId: id };
|
|
172
165
|
}
|
|
173
|
-
export function
|
|
174
|
-
if (sessionId && isSessionConflictResult(result)) {
|
|
175
|
-
runner.sessions.evict?.(sessionId);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
export async function runInBackground(runner, spec, runStore, taskId, metrics, principal, verify, cascade, instrumentDegenerate, planCacheProbe, persistThinking = false, inflight, preemptable, steerable, modelUsage, elicitation, elicitOwner, captureTurnAnchor, fleetPublisher, captureUserMessageAnchor, question, workflowCompletionInbox, subagentSteer, completionDiagLog, sendUserFile, promptManifests, approval, parkCheckpointReader, pendingEngineNotices, trustedInternals, userInputLedger) {
|
|
166
|
+
export async function runInBackground(runner, spec, runStore, taskId, metrics, principal, verify, cascade, instrumentDegenerate, planCacheProbe, persistThinking = false, inflight, preemptable, steerable, modelUsage, elicitation, elicitOwner, captureTurnAnchor, fleetPublisher, captureUserMessageAnchor, question, workflowCompletionInbox, subagentSteer, completionDiagLog, sendUserFile, promptManifests, approval, parkCheckpointReader, pendingEngineNotices, trustedInternals, userInputLedger, acceptance) {
|
|
179
167
|
const startedAt = Date.now();
|
|
180
168
|
metrics?.addGauge("runs_active", 1);
|
|
181
169
|
fleetPublisher?.onStart();
|
|
@@ -338,7 +326,6 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
338
326
|
if (verify !== undefined) {
|
|
339
327
|
const result = await runWithVerification(runner, { ...spec, taskId, signal: cancelCtrl.signal }, verify, { ...fwdInternals, ...statusSeat });
|
|
340
328
|
await flush();
|
|
341
|
-
evictIfConflict(runner, spec.sessionId, result);
|
|
342
329
|
await flushPromptManifest();
|
|
343
330
|
const safe = await withModelUsage(sealResultForExit(result));
|
|
344
331
|
await append("done", { result: safe });
|
|
@@ -346,13 +333,13 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
346
333
|
const plane = outwardPlane(safe.terminal);
|
|
347
334
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
348
335
|
await releaseKeyIfZeroWorkRefusal(runStore, taskId, safe);
|
|
336
|
+
acceptance?.settle(safe);
|
|
349
337
|
recordDone(safe, plane.status);
|
|
350
338
|
return;
|
|
351
339
|
}
|
|
352
340
|
if (cascade !== undefined) {
|
|
353
341
|
const result = await runCascade(runner, { ...spec, taskId, signal: cancelCtrl.signal }, cascade, { ...fwdInternals, ...statusSeat });
|
|
354
342
|
await flush();
|
|
355
|
-
evictIfConflict(runner, spec.sessionId, result);
|
|
356
343
|
await flushPromptManifest();
|
|
357
344
|
const safe = await withModelUsage(sealResultForExit(result));
|
|
358
345
|
await append("done", { result: safe });
|
|
@@ -360,6 +347,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
360
347
|
const plane = outwardPlane(safe.terminal);
|
|
361
348
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
362
349
|
await releaseKeyIfZeroWorkRefusal(runStore, taskId, safe);
|
|
350
|
+
acceptance?.settle(safe);
|
|
363
351
|
recordDone(safe, plane.status);
|
|
364
352
|
return;
|
|
365
353
|
}
|
|
@@ -373,6 +361,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
373
361
|
}
|
|
374
362
|
fleetPublisher?.onEvent(ev);
|
|
375
363
|
await userInputLedger?.observeLegEvent(stream, ev);
|
|
364
|
+
acceptance?.observe(ev);
|
|
376
365
|
await flushPromptManifest();
|
|
377
366
|
switch (ev.type) {
|
|
378
367
|
case "text_delta":
|
|
@@ -406,7 +395,6 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
406
395
|
await parkNeedsReview(ev.result);
|
|
407
396
|
continue;
|
|
408
397
|
}
|
|
409
|
-
evictIfConflict(runner, spec.sessionId, ev.result);
|
|
410
398
|
{
|
|
411
399
|
const settled = causeAtDone !== undefined ? cancelledResult(causeAtDone, ev.result) : ev.result;
|
|
412
400
|
settledAsCancel = failureCodeOf(settled.terminal) === CANCELLED_CODE;
|
|
@@ -491,6 +479,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
491
479
|
settledAsCancel = failureCodeOf(reached.result.terminal) === CANCELLED_CODE;
|
|
492
480
|
await runStore.setTerminal(taskId, plane.status, reached.result, plane.errorMessage ?? null);
|
|
493
481
|
await releaseKeyIfZeroWorkRefusal(runStore, taskId, reached.result);
|
|
482
|
+
acceptance?.settle(reached.result);
|
|
494
483
|
}
|
|
495
484
|
else if (reached?.kind === "suspended") {
|
|
496
485
|
await runStore.setSuspended(taskId, reached.result);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** `K8S_SANDBOX_IPV6` 闭集:`enabled` = pod 保留平台给的 IPv6(pod spec 与未引入本旋钮时逐字同形);`disabled` = pod 级 sysctl 关掉 IPv6。 */
|
|
2
|
+
export declare const SANDBOX_IPV6_WORDS: readonly ["enabled", "disabled"];
|
|
3
|
+
export type SandboxIpv6 = (typeof SANDBOX_IPV6_WORDS)[number];
|
|
4
|
+
/** 旋钮缺席时的姿态(缺省不动 pod:关 v6 要每个节点的 kubelet 放行这两只 sysctl,没放行的集群 pod 起不来)。 */
|
|
5
|
+
export declare const SANDBOX_IPV6_DEFAULT: SandboxIpv6;
|
|
6
|
+
/** 一只 pod 级 sysctl(k8s `PodSecurityContext.sysctls[]` 的形)。 */
|
|
7
|
+
export interface PodSysctl {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly value: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 沙箱 pod 的 sysctl 只有这一个属主:`disabled` ⇒ `all`(已有接口)与 `default`(之后建的接口)都关 IPv6;`enabled` ⇒ 不铸。
|
|
13
|
+
* 两只都不在 k8s 的 safe sysctl 表里 ⇒ 每个节点 kubelet 要 `--allowed-unsafe-sysctls` 点名它们,否则 pod 起不来(车道连沙箱失败,响亮)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function sandboxPodSysctls(ipv6: SandboxIpv6): readonly PodSysctl[] | undefined;
|
|
16
|
+
/** 自检读的两张表(车道侧各一只 GET)。 */
|
|
17
|
+
export type EgressPolicyRead = "nodes" | "networkpolicies";
|
|
18
|
+
/** 节点地址里自检判的两类(`Hostname` / `InternalDNS` / `ExternalDNS` 不是地址)。 */
|
|
19
|
+
type NodeAddressType = "InternalIP" | "ExternalIP";
|
|
20
|
+
export type SandboxEgressGap = {
|
|
21
|
+
readonly kind: "node_address_open";
|
|
22
|
+
readonly node: string;
|
|
23
|
+
readonly addressType: NodeAddressType;
|
|
24
|
+
readonly address: string;
|
|
25
|
+
readonly policy: string;
|
|
26
|
+
readonly ports: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "policy_absent";
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "ipv6_enabled";
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "unreadable";
|
|
33
|
+
readonly what: EgressPolicyRead;
|
|
34
|
+
readonly detail: string;
|
|
35
|
+
};
|
|
36
|
+
/** 一只 GET 的读数:答了 2xx ⇒ 体;否则一句为什么读不到(HTTP 码 / 错误句)。 */
|
|
37
|
+
export type ApiRead = {
|
|
38
|
+
readonly ok: true;
|
|
39
|
+
readonly json: unknown;
|
|
40
|
+
} | {
|
|
41
|
+
readonly ok: false;
|
|
42
|
+
readonly detail: string;
|
|
43
|
+
};
|
|
44
|
+
export interface EgressPolicyInputs {
|
|
45
|
+
readonly nodes: ApiRead;
|
|
46
|
+
readonly policies: ApiRead;
|
|
47
|
+
/** 沙箱 pod 的标签(车道建 pod 时铸的那一份)。 */
|
|
48
|
+
readonly podLabels: Readonly<Record<string, string>>;
|
|
49
|
+
readonly ipv6: SandboxIpv6;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 读数 → 缺口(纯函数、全函数:不抛;顺序:读不到的表 → 策略缺席 → 逐节点逐地址 → IPv6)。一张表读不到,只跳过要它的那一判;另一张照判。
|
|
53
|
+
*/
|
|
54
|
+
export declare function sandboxEgressGaps(inputs: EgressPolicyInputs): SandboxEgressGap[];
|
|
55
|
+
/** 读数的说法:每个缺口一行 warn `sandbox_egress_policy_gap` + 一次 `recordFailOpen`(P-DEBT);零缺口一行 info `sandbox_egress_policy_checked`。 */
|
|
56
|
+
export declare function sayEgressPolicyReading(gaps: readonly SandboxEgressGap[], logger: {
|
|
57
|
+
info(msg: string, fields?: Record<string, unknown>): void;
|
|
58
|
+
warn(msg: string, fields?: Record<string, unknown>): void;
|
|
59
|
+
} | undefined, namespace: string): void;
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=sandbox-egress-policy.d.ts.map
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { BlockList, isIP } from "node:net";
|
|
2
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
3
|
+
export const SANDBOX_IPV6_WORDS = ["enabled", "disabled"];
|
|
4
|
+
export const SANDBOX_IPV6_DEFAULT = "enabled";
|
|
5
|
+
export function sandboxPodSysctls(ipv6) {
|
|
6
|
+
switch (ipv6) {
|
|
7
|
+
case "enabled":
|
|
8
|
+
return undefined;
|
|
9
|
+
case "disabled":
|
|
10
|
+
return [
|
|
11
|
+
{ name: "net.ipv6.conf.all.disable_ipv6", value: "1" },
|
|
12
|
+
{ name: "net.ipv6.conf.default.disable_ipv6", value: "1" },
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class Unjudgeable extends Error {
|
|
17
|
+
}
|
|
18
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19
|
+
function recordAt(v, where) {
|
|
20
|
+
if (!isRecord(v))
|
|
21
|
+
throw new Unjudgeable(`${where} is not an object`);
|
|
22
|
+
return v;
|
|
23
|
+
}
|
|
24
|
+
function optRecordAt(v, where) {
|
|
25
|
+
return v === undefined || v === null ? undefined : recordAt(v, where);
|
|
26
|
+
}
|
|
27
|
+
function arrayAt(v, where) {
|
|
28
|
+
if (!Array.isArray(v))
|
|
29
|
+
throw new Unjudgeable(`${where} is not a list`);
|
|
30
|
+
return v;
|
|
31
|
+
}
|
|
32
|
+
function optArrayAt(v, where) {
|
|
33
|
+
return v === undefined || v === null ? undefined : arrayAt(v, where);
|
|
34
|
+
}
|
|
35
|
+
function stringAt(v, where) {
|
|
36
|
+
if (typeof v !== "string")
|
|
37
|
+
throw new Unjudgeable(`${where} is not a string`);
|
|
38
|
+
return v;
|
|
39
|
+
}
|
|
40
|
+
function listItems(json, what) {
|
|
41
|
+
const items = isRecord(json) ? json.items : undefined;
|
|
42
|
+
if (!Array.isArray(items))
|
|
43
|
+
throw new Unjudgeable(`${what} answer carries no items list`);
|
|
44
|
+
return items;
|
|
45
|
+
}
|
|
46
|
+
const SELECTOR_OPERATORS = ["In", "NotIn", "Exists", "DoesNotExist"];
|
|
47
|
+
function readSelector(v, where) {
|
|
48
|
+
const sel = optRecordAt(v, where) ?? {};
|
|
49
|
+
const labels = optRecordAt(sel.matchLabels, `${where}.matchLabels`) ?? {};
|
|
50
|
+
const matchLabels = {};
|
|
51
|
+
for (const [k, val] of Object.entries(labels))
|
|
52
|
+
matchLabels[k] = stringAt(val, `${where}.matchLabels.${k}`);
|
|
53
|
+
const matchExpressions = (optArrayAt(sel.matchExpressions, `${where}.matchExpressions`) ?? []).map((e, i) => {
|
|
54
|
+
const x = recordAt(e, `${where}.matchExpressions[${i}]`);
|
|
55
|
+
const operator = stringAt(x.operator, `${where}.matchExpressions[${i}].operator`);
|
|
56
|
+
if (!SELECTOR_OPERATORS.includes(operator))
|
|
57
|
+
throw new Unjudgeable(`${where} operator ${JSON.stringify(operator)} is not one of ${SELECTOR_OPERATORS.join(" / ")}`);
|
|
58
|
+
return { key: stringAt(x.key, `${where}.matchExpressions[${i}].key`), operator, values: (optArrayAt(x.values, `${where}.matchExpressions[${i}].values`) ?? []).map((y, j) => stringAt(y, `${where}.matchExpressions[${i}].values[${j}]`)) };
|
|
59
|
+
});
|
|
60
|
+
return { matchLabels, matchExpressions };
|
|
61
|
+
}
|
|
62
|
+
function selects(sel, labels) {
|
|
63
|
+
for (const [k, v] of Object.entries(sel.matchLabels))
|
|
64
|
+
if (labels[k] !== v)
|
|
65
|
+
return false;
|
|
66
|
+
for (const e of sel.matchExpressions) {
|
|
67
|
+
const has = Object.prototype.hasOwnProperty.call(labels, e.key);
|
|
68
|
+
switch (e.operator) {
|
|
69
|
+
case "In":
|
|
70
|
+
if (!has || !e.values.includes(labels[e.key]))
|
|
71
|
+
return false;
|
|
72
|
+
break;
|
|
73
|
+
case "NotIn":
|
|
74
|
+
if (has && e.values.includes(labels[e.key]))
|
|
75
|
+
return false;
|
|
76
|
+
break;
|
|
77
|
+
case "Exists":
|
|
78
|
+
if (!has)
|
|
79
|
+
return false;
|
|
80
|
+
break;
|
|
81
|
+
case "DoesNotExist":
|
|
82
|
+
if (has)
|
|
83
|
+
return false;
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
throw new Unjudgeable(`podSelector operator ${JSON.stringify(e.operator)} is not one of ${SELECTOR_OPERATORS.join(" / ")}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
function readCidr(v, where) {
|
|
92
|
+
const cidr = stringAt(v, where);
|
|
93
|
+
parseCidr(cidr);
|
|
94
|
+
return cidr;
|
|
95
|
+
}
|
|
96
|
+
function readRule(v, where) {
|
|
97
|
+
const r = recordAt(v, where);
|
|
98
|
+
const to = optArrayAt(r.to, `${where}.to`);
|
|
99
|
+
const blocks = [];
|
|
100
|
+
for (const [i, peer] of (to ?? []).entries()) {
|
|
101
|
+
const ip = optRecordAt(recordAt(peer, `${where}.to[${i}]`).ipBlock, `${where}.to[${i}].ipBlock`);
|
|
102
|
+
if (ip === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
blocks.push({ cidr: readCidr(ip.cidr, `${where}.to[${i}].ipBlock.cidr`), except: (optArrayAt(ip.except, `${where}.to[${i}].ipBlock.except`) ?? []).map((x, j) => readCidr(x, `${where}.to[${i}].ipBlock.except[${j}]`)) });
|
|
105
|
+
}
|
|
106
|
+
const ports = optArrayAt(r.ports, `${where}.ports`);
|
|
107
|
+
const portText = ports === undefined || ports.length === 0
|
|
108
|
+
? "all"
|
|
109
|
+
: ports
|
|
110
|
+
.map((p, i) => {
|
|
111
|
+
const x = recordAt(p, `${where}.ports[${i}]`);
|
|
112
|
+
return `${typeof x.protocol === "string" ? x.protocol : "TCP"}/${typeof x.port === "number" || typeof x.port === "string" ? x.port : "*"}${typeof x.endPort === "number" ? `-${x.endPort}` : ""}`;
|
|
113
|
+
})
|
|
114
|
+
.join(",");
|
|
115
|
+
return { to: to === undefined || to.length === 0 ? undefined : blocks, ports: portText };
|
|
116
|
+
}
|
|
117
|
+
function isolatesEgress(spec, where) {
|
|
118
|
+
const types = optArrayAt(spec.policyTypes, `${where}.policyTypes`);
|
|
119
|
+
return types !== undefined ? types.map((t, i) => stringAt(t, `${where}.policyTypes[${i}]`)).includes("Egress") : spec.egress !== undefined && spec.egress !== null;
|
|
120
|
+
}
|
|
121
|
+
function egressPolicies(json, podLabels) {
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const [i, item] of listItems(json, "networkpolicies").entries()) {
|
|
124
|
+
const p = recordAt(item, `networkpolicies[${i}]`);
|
|
125
|
+
const name = stringAt(recordAt(p.metadata, `networkpolicies[${i}].metadata`).name, `networkpolicies[${i}].metadata.name`);
|
|
126
|
+
const spec = recordAt(p.spec, `networkpolicy ${name} spec`);
|
|
127
|
+
if (!isolatesEgress(spec, `networkpolicy ${name}`) || !selects(readSelector(spec.podSelector, `networkpolicy ${name} podSelector`), podLabels))
|
|
128
|
+
continue;
|
|
129
|
+
out.push({ name, rules: (optArrayAt(spec.egress, `networkpolicy ${name} egress`) ?? []).map((r, j) => readRule(r, `networkpolicy ${name} egress[${j}]`)) });
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
function nodeAddresses(json) {
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const [i, item] of listItems(json, "nodes").entries()) {
|
|
136
|
+
const n = recordAt(item, `nodes[${i}]`);
|
|
137
|
+
const node = stringAt(recordAt(n.metadata, `nodes[${i}].metadata`).name, `nodes[${i}].metadata.name`);
|
|
138
|
+
const addresses = arrayAt(recordAt(n.status, `node ${node} status`).addresses, `node ${node} status.addresses`);
|
|
139
|
+
let seen = 0;
|
|
140
|
+
for (const [j, a] of addresses.entries()) {
|
|
141
|
+
const x = recordAt(a, `node ${node} status.addresses[${j}]`);
|
|
142
|
+
const type = stringAt(x.type, `node ${node} status.addresses[${j}].type`);
|
|
143
|
+
if (type !== "InternalIP" && type !== "ExternalIP")
|
|
144
|
+
continue;
|
|
145
|
+
const address = stringAt(x.address, `node ${node} status.addresses[${j}].address`);
|
|
146
|
+
if (isIP(address) === 0)
|
|
147
|
+
throw new Unjudgeable(`node ${node} ${type} ${JSON.stringify(address)} is not an IP address`);
|
|
148
|
+
out.push({ node, addressType: type, address });
|
|
149
|
+
seen++;
|
|
150
|
+
}
|
|
151
|
+
if (seen === 0)
|
|
152
|
+
throw new Unjudgeable(`node ${node} reports no InternalIP / ExternalIP`);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function parseCidr(cidr) {
|
|
157
|
+
const slash = cidr.indexOf("/");
|
|
158
|
+
const net = slash < 0 ? cidr : cidr.slice(0, slash);
|
|
159
|
+
const fam = isIP(net);
|
|
160
|
+
const max = fam === 6 ? 128 : 32;
|
|
161
|
+
const prefix = slash < 0 ? max : Number(cidr.slice(slash + 1));
|
|
162
|
+
if (fam === 0 || !/^\d+$/.test(slash < 0 ? String(max) : cidr.slice(slash + 1)) || prefix > max)
|
|
163
|
+
throw new Unjudgeable(`ipBlock cidr ${JSON.stringify(cidr)} does not parse`);
|
|
164
|
+
return { net, prefix, family: fam === 6 ? "ipv6" : "ipv4" };
|
|
165
|
+
}
|
|
166
|
+
function inCidr(address, cidr) {
|
|
167
|
+
const c = parseCidr(cidr);
|
|
168
|
+
if ((isIP(address) === 6 ? "ipv6" : "ipv4") !== c.family)
|
|
169
|
+
return false;
|
|
170
|
+
const list = new BlockList();
|
|
171
|
+
list.addSubnet(c.net, c.prefix, c.family);
|
|
172
|
+
return list.check(address, c.family);
|
|
173
|
+
}
|
|
174
|
+
function ruleAllows(rule, address) {
|
|
175
|
+
if (rule.to === undefined)
|
|
176
|
+
return true;
|
|
177
|
+
return rule.to.some((b) => inCidr(address, b.cidr) && !b.except.some((x) => inCidr(address, x)));
|
|
178
|
+
}
|
|
179
|
+
function readTable(read, what, parse, gaps) {
|
|
180
|
+
if (!read.ok) {
|
|
181
|
+
gaps.push({ kind: "unreadable", what, detail: read.detail });
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return parse(read.json);
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
gaps.push({ kind: "unreadable", what, detail: e instanceof Unjudgeable ? e.message : `answer not understood: ${e instanceof Error ? e.message : String(e)}` });
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export function sandboxEgressGaps(inputs) {
|
|
193
|
+
const gaps = [];
|
|
194
|
+
const policies = readTable(inputs.policies, "networkpolicies", (json) => egressPolicies(json, inputs.podLabels), gaps);
|
|
195
|
+
const addresses = readTable(inputs.nodes, "nodes", nodeAddresses, gaps);
|
|
196
|
+
if (policies !== undefined && policies.length === 0)
|
|
197
|
+
gaps.push({ kind: "policy_absent" });
|
|
198
|
+
if (policies !== undefined && policies.length > 0 && addresses !== undefined) {
|
|
199
|
+
for (const a of addresses) {
|
|
200
|
+
for (const p of policies) {
|
|
201
|
+
const rule = p.rules.find((r) => ruleAllows(r, a.address));
|
|
202
|
+
if (rule !== undefined) {
|
|
203
|
+
gaps.push({ kind: "node_address_open", node: a.node, addressType: a.addressType, address: a.address, policy: p.name, ports: rule.ports });
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (inputs.ipv6 === "enabled")
|
|
210
|
+
gaps.push({ kind: "ipv6_enabled" });
|
|
211
|
+
return gaps;
|
|
212
|
+
}
|
|
213
|
+
function effectOf(gap) {
|
|
214
|
+
switch (gap.kind) {
|
|
215
|
+
case "node_address_open":
|
|
216
|
+
return "sandbox pods can reach this node address through the egress policy — add it to sandbox.networkPolicy.nodeCidrs (kube-up lists every node's addresses) or narrow the allowing rule";
|
|
217
|
+
case "policy_absent":
|
|
218
|
+
return "no NetworkPolicy selects the sandbox pods for egress — the policy layer does not restrict sandbox egress (chart sandbox.networkPolicy.enabled, or the CNI's own policy)";
|
|
219
|
+
case "ipv6_enabled":
|
|
220
|
+
return "sandbox pods keep IPv6 — an enforcement layer that filters only IPv4 leaves the pods' IPv6 (link-local included) unfiltered; K8S_SANDBOX_IPV6=disabled turns it off in the pod (needs the two sysctls allowed by every node's kubelet)";
|
|
221
|
+
case "unreadable":
|
|
222
|
+
return `the ${gap.what} could not be read — this part of the egress check did not run (the server's k8s account needs ${gap.what === "nodes" ? "get/list on nodes (cluster scope)" : "get/list on networkpolicies in the sandbox namespace"})`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
export function sayEgressPolicyReading(gaps, logger, namespace) {
|
|
226
|
+
if (gaps.length === 0) {
|
|
227
|
+
logger?.info("sandbox_egress_policy_checked", { namespace, gaps: 0 });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
for (const gap of gaps) {
|
|
231
|
+
recordFailOpen("server.sandbox-egress.policy-gap", `kind=${gap.kind}${gap.kind === "unreadable" ? ` what=${gap.what}` : ""}`);
|
|
232
|
+
logger?.warn("sandbox_egress_policy_gap", { namespace, ...gap, effect: effectOf(gap) });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=sandbox-egress-policy.js.map
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
* 列表里的缺席),码位各不相同,不由本表统一。
|
|
30
30
|
* · `invalid_entry` / `invalid_fork_target` / `placement_refused` —— 留白:本仓今天没有一条 wire 口把它们作为自己的
|
|
31
31
|
* 应答(它们在 core 的追加 / 分叉 / 放置面上抛,落进各站点既有的处置或通用 500)。给它们起名是另一件事、另一张票。
|
|
32
|
-
* · `storage` / `conflict` / `unknown` —— 留白:`conflict`
|
|
33
|
-
* `
|
|
32
|
+
* · `storage` / `conflict` / `unknown` —— 留白:`conflict` 是 run 的写撞上真并发写,落成那条 run 的 `failed` 终局
|
|
33
|
+
* (`terminal.code: "conflict"`),提交腿不另作处置;`storage` / `unknown` 是故障类,通用 500 就是它们今天的应答。
|
|
34
34
|
*
|
|
35
35
|
* core 给闭集加一个词 ⇒ 下面的 `switch` 编译红,由人裁定它在两面上的答复,而不是让它静默落进某个恰好存在的臂
|
|
36
36
|
* ([ref] 词表纪律;同形先例 `session-policy-wire.ts` 的 `sessionPolicyErrorHttp`)。
|
package/dist/session-titler.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated session titles.
|
|
3
3
|
*
|
|
4
|
-
* SHAPE: fire-and-forget
|
|
5
|
-
*
|
|
4
|
+
* SHAPE: fire-and-forget once the ENGINE ACCEPTS a submitted run whose session has NO title — the one caller is the
|
|
5
|
+
* submission's acceptance seat (`run-acceptance.ts`: the objective entered the conversation, or a result-only verify /
|
|
6
|
+
* cascade leg came back having done work). A run the engine refuses before acceptance never reaches the titler (no
|
|
7
|
+
* model call for a refused input). No need to wait for the run to finish, and the web Recents gets the title sooner. Fills a NULL title only (`setTitleIfNull`,
|
|
6
8
|
* store-side `WHERE title IS NULL`) — the user writes the SAME column unconditionally (`PATCH /v1/sessions/:id` →
|
|
7
9
|
* `patchSessionMeta`): a user-set title is never overwritten (the probe below reads "titled" and skips the model
|
|
8
10
|
* call), and a title the user cleared (`title: null` ⇒ NULL) is filled again at the next submit (law: `session-row.ts`).
|
|
@@ -25,7 +27,7 @@ export interface SessionTitlerDeps {
|
|
|
25
27
|
setTitleIfNull(sessionId: string, title: string): Promise<boolean>;
|
|
26
28
|
/** Cheap pre-LLM probe: "titled" = skip (a restart/evicted-seen/another replica
|
|
27
29
|
* already paid — don't re-spend an LLM call); "untitled" = generate; "none" = the session_meta row hasn't
|
|
28
|
-
* been REGISTERED
|
|
30
|
+
* been REGISTERED (or was deleted meanwhile) — retry after a grace window,
|
|
29
31
|
* and if still absent, release the seen slot so the NEXT submit retries (never a permanently-untitled
|
|
30
32
|
* session via a swallowed early UPDATE-0). Local lane has no row-existence dimension → never "none". */
|
|
31
33
|
probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
|
|
@@ -50,7 +50,7 @@ type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded,
|
|
|
50
50
|
type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "cause" | "inputHasBidi" | "rememberRefused";
|
|
51
51
|
type _GuardAskFrame = AssertAllKeysHandled<Exclude<keyof ToolApprovalFrame, AskProjected | AskFrameServerMinted>>;
|
|
52
52
|
type _GuardAskFrameReverse = AssertAllKeysHandled<Exclude<AskProjected | AskFrameServerMinted, keyof ToolApprovalFrame>>;
|
|
53
|
-
type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta" | "tool_progress" | "tool_disclosure";
|
|
53
|
+
type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta" | "tool_progress" | "tool_disclosure" | "task_started";
|
|
54
54
|
type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
|
|
55
55
|
type _GuardTaskEventReverse = AssertAllKeysHandled<Exclude<TaskEventHandled, TaskEvent["type"]>>;
|
|
56
56
|
type MailboxProjected = "seq" | "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
|
|
@@ -149,7 +149,8 @@ type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueue
|
|
|
149
149
|
type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
|
|
150
150
|
type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
|
|
151
151
|
type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "approvalReach" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills" | "outputStyle" | "allowedTools" | "allowTools";
|
|
152
|
-
|
|
152
|
+
/** 导出:请求体键闭集门(`request-key-closure.ts`)据它锁「本仓记为未接线的引擎座,提交口不许受理」—— 两处分类只能同向。 */
|
|
153
|
+
export type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "writeProtectedAdditions" | "complianceAdditionalDenies" | "modelRole" | "enableBlockedReport" | "userAttachments";
|
|
153
154
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
154
155
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
155
156
|
type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles" | "responseModel" | "compaction";
|
|
@@ -39,9 +39,15 @@ export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" |
|
|
|
39
39
|
/** [ref](core 7.18.0):本条腿的**延迟工具普查**快照(policy / thresholdPercent? / deferred[] / activated[])。
|
|
40
40
|
* 与 `tool_roster_delta` 同判、同理由——**快照语义**(整只替换,回放零合并),而开腿那一份住在
|
|
41
41
|
* `wiring_manifest` 之后的首帧、**之后每一次激活**只活在本帧上。整帧缺席 = 这条腿什么都没延迟。
|
|
42
|
-
* 🔴 `tool_progress`([ref])
|
|
43
|
-
*
|
|
44
|
-
|
|
42
|
+
* 🔴 `tool_progress`([ref])**刻意不在本表**,理由是它自己的、不是「core 标了 LIVE ONLY」:那一条只是**引擎**的义务
|
|
43
|
+
* (不进会话树 / 检查点 / 续跑重放),本仓的账本是异步车道的活流与断线重连面,律是「EPHEMERAL 帧带着回放面没有第二来源的
|
|
44
|
+
* 事实 ⇒ 落账」(`status` / `task_progress` / `task_started` 都落)。`tool_progress` 的事实由同一只调用的 `tool_end` 推出
|
|
45
|
+
* (真源在那里;帧的缺席也从不是证据 —— 一个周期内跑完的调用零帧),所以它不落。 */
|
|
46
|
+
| "tool_disclosure" | "human_input"
|
|
47
|
+
/** core 7.50.0:委派子代**起跑**帧(每周期一只,先于子代的首个 `task_progress`)。按上面那一条律落账:
|
|
48
|
+
* `agentType` / `rootSessionId` / `parentSessionId` / `startedAt` 只骑这一帧,`task_progress` 上没有 ⇒ 不落则异步车道
|
|
49
|
+
* (`POST /v1/runs` 的活流就是 `GET /v1/runs/:id/events` 的账本 tail)永远读不到。两条 durable 腿主流 + forward 道各一处写口。 */
|
|
50
|
+
| "task_started" | "task_progress" | "task_notification" | "turn_end"
|
|
45
51
|
/** park 行名**两个**(`appendParked(kind)` 逐字取自 `result.status`)—— 运行期闭集见
|
|
46
52
|
* {@link PARK_LEDGER_EVENT_TYPES}(本联合是它的类型面,两者由下方的双向断言锁步)。 */
|
|
47
53
|
| "suspended" | "needs_review" | "done" | "failed" | "suggestions" | "model_usage" | "prompt_assembled" | "config_assembled" | "file_link"
|
|
@@ -97,6 +97,13 @@ export interface LedgerSink {
|
|
|
97
97
|
* ⚠️ 用它的前提是**这条腿的全部写口都走它**(混用一个绕开链的写口 = 病灶原样保留)。
|
|
98
98
|
*/
|
|
99
99
|
export declare function createSerialLedgerAppend(appendEvent: (seq: number, type: LedgerEventType, data: unknown) => Promise<void>, startSeq: number): (type: LedgerEventType, data: unknown) => Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* durable 腿 forward 道(fire-and-forget,同步回调里不能 await)的写失败出口 —— 一只,两处用:本汇的 `onForwardEvent`(bg 腿 +
|
|
102
|
+
* sync detach 车道 + 续跑腿的 verify 臂)与续跑腿自己的 forward 回调(`http/resume-legs.ts`,那里另有既有的逐次 warn 行)。
|
|
103
|
+
* F 类留痕 `server.ledger.forward-append-failed`:这一帧永远不在回放面(子代的起跑帧 / tick / 工具卡),run 照跑、不改任何裁决。
|
|
104
|
+
* 返回 `.catch` 用的处理器(产物是函数 ⇒ `create*`)。
|
|
105
|
+
*/
|
|
106
|
+
export declare function createForwardAppendFailedNote(type: string): (err: unknown) => void;
|
|
100
107
|
export declare function createLedgerSink(opts: {
|
|
101
108
|
/** The seq-stamped durable append — typically `(seq, type, data) => runStore.appendEvent(taskId, seq, type, data)`. */
|
|
102
109
|
appendEvent: (seq: number, type: LedgerEventType, data: unknown) => Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {} from "@sema-agent/core";
|
|
2
2
|
import { outwardPlane } from "../terminal.js";
|
|
3
|
-
import {
|
|
3
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
4
|
+
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
4
5
|
import { NotifiedKeys, taskNotificationStreamKeyOf } from "../orchestration/workflow-completion-inbox.js";
|
|
5
6
|
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
6
7
|
import { isModelSegmentBoundary, reasoningDeltaBytes, reasoningSegmentEventId } from "./model-segment.js";
|
|
@@ -13,6 +14,9 @@ export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
|
13
14
|
return step;
|
|
14
15
|
};
|
|
15
16
|
}
|
|
17
|
+
export function createForwardAppendFailedNote(type) {
|
|
18
|
+
return (err) => recordFailOpen("server.ledger.forward-append-failed", `type=${type} err=${err instanceof Error ? err.message : String(err)}`);
|
|
19
|
+
}
|
|
16
20
|
export function createLedgerSink(opts) {
|
|
17
21
|
const keys = opts.notifiedKeys ?? new NotifiedKeys();
|
|
18
22
|
let seq = 0;
|
|
@@ -114,6 +118,9 @@ export function createLedgerSink(opts) {
|
|
|
114
118
|
case "human_input":
|
|
115
119
|
await append("human_input", humanInputEventData(ev));
|
|
116
120
|
break;
|
|
121
|
+
case "task_started":
|
|
122
|
+
await append("task_started", taskStartedEventData(ev));
|
|
123
|
+
break;
|
|
117
124
|
case "task_progress":
|
|
118
125
|
await append("task_progress", taskProgressEventData(ev));
|
|
119
126
|
break;
|
|
@@ -140,13 +147,16 @@ export function createLedgerSink(opts) {
|
|
|
140
147
|
const onForwardEvent = (e) => {
|
|
141
148
|
const t = e.type;
|
|
142
149
|
if (t === "task_progress") {
|
|
143
|
-
void append("task_progress", taskProgressEventData(e)).catch(()
|
|
150
|
+
void append("task_progress", taskProgressEventData(e)).catch(createForwardAppendFailedNote(t));
|
|
151
|
+
}
|
|
152
|
+
else if (t === "task_started") {
|
|
153
|
+
void append("task_started", taskStartedEventData(e)).catch(createForwardAppendFailedNote(t));
|
|
144
154
|
}
|
|
145
155
|
else if (t === "tool_start") {
|
|
146
|
-
void append("tool_start", toolStartEventData(e)).catch(()
|
|
156
|
+
void append("tool_start", toolStartEventData(e)).catch(createForwardAppendFailedNote(t));
|
|
147
157
|
}
|
|
148
158
|
else if (t === "tool_end") {
|
|
149
|
-
void append("tool_end", toolEndEventData(e)).catch(()
|
|
159
|
+
void append("tool_end", toolEndEventData(e)).catch(createForwardAppendFailedNote(t));
|
|
150
160
|
}
|
|
151
161
|
};
|
|
152
162
|
const appendStatus = (ev) => {
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -290,14 +290,13 @@ export declare function toolEndEventData(ev: {
|
|
|
290
290
|
/**
|
|
291
291
|
* `tool_progress` 的白名单投影(core 7.18.0 [ref])—— 一只**仍在跑**的工具调用的存活 tick。
|
|
292
292
|
*
|
|
293
|
-
* ## 这一帧只上 live,不落账(
|
|
293
|
+
* ## 这一帧只上 live,不落账(判据是回放面有没有第二来源,不是「core 标了 LIVE ONLY」)
|
|
294
294
|
* core `@contract tool_progress.ephemeral` 逐字:「LIVE ONLY, like `task_progress`/`status`: core never
|
|
295
|
-
* persists this frame (no session-tree entry, no checkpoint state, no resume replay)
|
|
296
|
-
* `
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
* **不等于** `tool_end` 的 duration)。
|
|
295
|
+
* persists this frame (no session-tree entry, no checkpoint state, no resume replay)」—— 那是**引擎**的义务;
|
|
296
|
+
* 本仓的律是「EPHEMERAL 帧带着回放面没有第二来源的事实 ⇒ 落账」(`status` / `task_progress` / `task_started` 都落)。
|
|
297
|
+
* 本帧的事实**有**第二来源:`@contract tool_progress.cadence` 逐字「A call that finishes inside one interval emits
|
|
298
|
+
* NO frame at all」⇒ **帧的缺席从来不是证据**;落账会让回放面凭空多出一份「这只调用跑了多久」的账,而那件事的
|
|
299
|
+
* 真源是 `tool_end`(core 明写 `elapsedTimeSeconds` **不等于** `tool_end` 的 duration)。
|
|
301
300
|
*
|
|
302
301
|
* ## 信任划线
|
|
303
302
|
* · `toolCallId` / `toolName` —— 调用 id(🔴 S-283 订正:出身是**服务商**在 tool_use / tool_calls 里给的,引擎缺席时自铸兜底、越界时折叠 ——
|
|
@@ -309,7 +308,8 @@ export declare function toolEndEventData(ev: {
|
|
|
309
308
|
* · `elapsedTimeSeconds` —— 引擎铸的整秒,单调、一 call 一个原点 ⇒ 有限数才铸键。
|
|
310
309
|
* · `output` —— 🔴 **UNTRUSTED RAW**,core 逐字「exactly as `tool_end.output`: process output that may
|
|
311
310
|
* carry secrets, paths or PII; core bounds only its SIZE, and a consumer MUST redact before persisting
|
|
312
|
-
* or displaying it」⇒ 与 `tool_end.output`
|
|
311
|
+
* or displaying it」⇒ 与 `tool_end.output` **同一只**投影 `projectToolOutput`(串形与文本块整只过 `redactDeep`;本帧只有串形,
|
|
312
|
+
* 媒体块那一臂在这里够不着),一个字节的例外都不开。
|
|
313
313
|
* · `totalLines` / `totalBytes` —— 引擎计数;core 的缺席律逐字:三者**同进同退**(在场=「观察到了输出」,
|
|
314
314
|
* 缺席=「一个字节都没观察到」,**从不是**「与上一帧相比没变」)。所以本投影按 `output` 的在场判三者:
|
|
315
315
|
* `output` 读不出 ⇒ 三键一起缺席,不单独留下两个计数(那会把「没输出」谎报成「有输出但读不出」)。
|
|
@@ -373,6 +373,47 @@ export declare function taskProgressEventData(ev: {
|
|
|
373
373
|
seq?: number;
|
|
374
374
|
model?: string;
|
|
375
375
|
}): Record<string, unknown>;
|
|
376
|
+
/**
|
|
377
|
+
* The `task_started` payload(core 7.50.0):一只委派子代**起跑**的那一帧 —— 每周期一只(首派 / 复活;耐久续跑腿不铸),
|
|
378
|
+
* 序在子代的 `wiring_manifest` 之后、它的首个 `task_progress` 与首次模型请求之前。
|
|
379
|
+
*
|
|
380
|
+
* **活面投、账本落**(与 `task_progress` / `status` 同一条律)。core 契约逐字「Live and ephemeral like `task_progress`:
|
|
381
|
+
* never persisted, never replayed on resume, absent from checkpoints」—— 那是**引擎**的义务(不进会话树、检查点、续跑重放)。
|
|
382
|
+
* 本服务的账本是异步车道的活流(`POST /v1/runs` 的观看者读的就是 `GET /v1/runs/:id/events` 的账本 tail)与断线重连面,
|
|
383
|
+
* 律是「**EPHEMERAL 帧带着回放面没有第二来源的事实 ⇒ 落账**」:`agentType` / `rootSessionId` / `parentSessionId` / `startedAt`
|
|
384
|
+
* 只骑这一帧,`task_progress` 上没有。(`tool_progress` 不落是同一条律的反面:它的事实由同一只调用的 `tool_end` 推出。)
|
|
385
|
+
* 调用点:三条 live 腿(sync 主流、sync forward 道、子代 tail 总线)+ 两条 durable 腿(账本汇、resume 腿)各自的主流臂与
|
|
386
|
+
* forward 道 —— 即 `task_progress` 的全部投影点,同一只构造器。
|
|
387
|
+
*
|
|
388
|
+
* 键的信任判与 `task_progress` 孪生**逐键同**(core 原话「every member is a value the run already speaks
|
|
389
|
+
* elsewhere — this frame introduces no new reading of any of them」⇒ 本仓也不铸第二种读法):
|
|
390
|
+
* · run / 会话 id、`seq`、`taskType`、`model`、`workflowRunId`、`agentType` —— 引擎值 verbatim(`agentType` 是解析过的子代类型名,
|
|
391
|
+
* 不是模型自由文本);`startedAt` 是有限数才上 wire;
|
|
392
|
+
* · `name` / `workflowAgentLabel` —— UNTRUSTED 展示文本 ⇒ `redactSecrets`(同 `task_progress.name` / `.workflowAgentLabel`);
|
|
393
|
+
* · `spawnName` —— 出生点校验过的队友**身份键** ⇒ verbatim(理由全文在 {@link taskProgressEventData} 的同名键注);
|
|
394
|
+
* · 身份两键经 `identityFields`(信封上的 `sourceTaskId` / `bgAgentId` 不由本族构造器投,见 `FRAME_ENVELOPE_KEYS`)。
|
|
395
|
+
* 缺席即缺席:可选键缺席不铸键(`taskType` 缺席 = 前台子代,禁读作某个默认种类;`seq` 缺席 = 无 `a*` 行,禁读作 cycle 1)。
|
|
396
|
+
*/
|
|
397
|
+
export declare function taskStartedEventData(ev: {
|
|
398
|
+
/** 判别位(入参就是那一帧;本构造器不投它 —— 帧名由调用点的 `type` 键 / 账本行名给)。 */
|
|
399
|
+
type?: "task_started";
|
|
400
|
+
taskId: string;
|
|
401
|
+
sessionId: string;
|
|
402
|
+
taskType?: string;
|
|
403
|
+
seq?: number;
|
|
404
|
+
workflowRunId?: string;
|
|
405
|
+
workflowAgentLabel?: string;
|
|
406
|
+
parentTaskId?: string;
|
|
407
|
+
parentSessionId?: string;
|
|
408
|
+
rootSessionId?: string;
|
|
409
|
+
name?: string;
|
|
410
|
+
spawnName?: string;
|
|
411
|
+
agentType?: string;
|
|
412
|
+
model: string;
|
|
413
|
+
startedAt: number;
|
|
414
|
+
eventId?: string;
|
|
415
|
+
parentToolCallId?: string;
|
|
416
|
+
}): Record<string, unknown>;
|
|
376
417
|
/** The `task_notification` payload (core 1.202 [ref] P2: a background task finished — this event is the
|
|
377
418
|
* LEDGER/display projection of the OBSERVED terminal state, emitted the moment core observes it. It does NOT
|
|
378
419
|
* attest that the `<task-notification>` XML has entered the model context: core's steer targets the NEXT TURN
|