@sema-agent/server 7.78.0 → 7.78.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/dist/approval-card.d.ts +24 -1
- package/dist/approval-card.js +13 -0
- package/dist/boot/resolve-spec.js +3 -0
- package/dist/http/routes/approvals-assistant.d.ts +22 -0
- package/dist/http/routes/approvals-assistant.js +4 -1
- package/dist/http/routes/tasks.js +4 -9
- package/dist/http/server.d.ts +28 -4
- package/dist/http/server.js +38 -12
- package/dist/request-key-closure.d.ts +30 -0
- package/dist/request-key-closure.js +56 -0
- package/dist/runs.js +3 -1
- package/dist/runtime-governance.d.ts +18 -4
- package/dist/runtime-governance.js +2 -2
- package/dist/spec-fields.d.ts +3 -1
- package/dist/spec-fields.js +4 -5
- package/dist/task-settings.d.ts +18 -16
- package/dist/task-settings.js +17 -29
- package/dist/tool-approval.d.ts +35 -3
- package/dist/tool-approval.js +41 -19
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/ledger-sink.js +12 -5
- package/dist/trace/model-segment.d.ts +32 -2
- package/dist/trace/model-segment.js +15 -5
- package/dist/trace/project.d.ts +37 -3
- package/dist/trace/project.js +14 -10
- package/package.json +3 -3
package/dist/task-settings.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { combinePolicies, createAllowDenyPolicy, createFsWriteGatePolicy, createPermissionRulePolicy, parseRuleText, tightenTaskSpec, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, ruleToolGrammarOf, } from "@sema-agent/core";
|
|
2
2
|
import { findUnmatchableToolNames, formatUnmatchableToolNames } from "./config.js";
|
|
3
3
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
4
|
+
import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "./request-key-closure.js";
|
|
4
5
|
import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
5
6
|
export function coercePermissionMode(raw) {
|
|
6
7
|
const strict = coercePermissionModeStrict(raw);
|
|
@@ -72,49 +73,31 @@ function cleanStringArray(v) {
|
|
|
72
73
|
const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
|
|
73
74
|
return out.length > 0 ? out : undefined;
|
|
74
75
|
}
|
|
75
|
-
export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "ultracode", "webSearch"];
|
|
76
|
+
export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "skillListingBudgetFraction", "skillListingMaxDescChars", "ultracode", "webSearch"];
|
|
76
77
|
export const TASK_SETTINGS_PERMISSION_KEYS = ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
|
|
77
78
|
export const TASK_SETTINGS_UNSUPPORTED_KEYS = ["apiKeyHelper", "crossSessionInbound", "dialogExpiry", "sandbox", "statusLine"];
|
|
78
79
|
export const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS = ["additionalDirectories", "disableBypassPermissionsMode"];
|
|
79
|
-
export const MAX_SETTINGS_KEY_ECHO = 20;
|
|
80
|
-
const MAX_SETTINGS_KEY_ECHO_CHARS = 64;
|
|
81
80
|
const ACCEPTED_TOP = new Set(TASK_SETTINGS_KEYS);
|
|
82
81
|
const ACCEPTED_PERM = new Set(TASK_SETTINGS_PERMISSION_KEYS);
|
|
83
82
|
const UNSUPPORTED_TOP = new Set(TASK_SETTINGS_UNSUPPORTED_KEYS);
|
|
84
83
|
const UNSUPPORTED_PERM = new Set(TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS);
|
|
85
|
-
function echoSettingsKey(prefix, key) {
|
|
86
|
-
const clean = key.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
87
|
-
return `${prefix}${clean.length > MAX_SETTINGS_KEY_ECHO_CHARS ? `${clean.slice(0, MAX_SETTINGS_KEY_ECHO_CHARS)}…` : clean}`;
|
|
88
|
-
}
|
|
89
84
|
export function taskSettingsKeyIssue(raw) {
|
|
90
85
|
if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
|
|
91
86
|
return null;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const scan = (bag, prefix, accepted, tolerated) => {
|
|
95
|
-
for (const k of Object.keys(bag)) {
|
|
96
|
-
if (accepted.has(k))
|
|
97
|
-
continue;
|
|
98
|
-
(tolerated.has(k) ? unsupported : unknown).push(echoSettingsKey(prefix, k));
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
scan(raw, "settings.", ACCEPTED_TOP, UNSUPPORTED_TOP);
|
|
87
|
+
const buckets = { unknown: [], unsupported: [] };
|
|
88
|
+
scanKeyClosure(raw, "settings.", ACCEPTED_TOP, UNSUPPORTED_TOP, buckets);
|
|
102
89
|
const perms = raw.permissions;
|
|
103
90
|
if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
|
|
104
|
-
|
|
91
|
+
scanKeyClosure(perms, "settings.permissions.", ACCEPTED_PERM, UNSUPPORTED_PERM, buckets);
|
|
105
92
|
}
|
|
93
|
+
const { unknown, unsupported } = buckets;
|
|
106
94
|
if (unknown.length === 0 && unsupported.length === 0)
|
|
107
95
|
return null;
|
|
108
96
|
unknown.sort();
|
|
109
97
|
unsupported.sort();
|
|
110
|
-
const clause = (names, label) => {
|
|
111
|
-
const shown = names.slice(0, MAX_SETTINGS_KEY_ECHO);
|
|
112
|
-
const more = names.length - shown.length;
|
|
113
|
-
return `${label}: ${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
114
|
-
};
|
|
115
98
|
const parts = [
|
|
116
|
-
...(unknown.length > 0 ? [
|
|
117
|
-
...(unsupported.length > 0 ? [
|
|
99
|
+
...(unknown.length > 0 ? [keyClosureClause(unknown, "unknown (not a settings key here — check the spelling)")] : []),
|
|
100
|
+
...(unsupported.length > 0 ? [keyClosureClause(unsupported, "unsupported (a CC settings key this service does not wire)")] : []),
|
|
118
101
|
];
|
|
119
102
|
const dirHint = unsupported.some((n) => n.endsWith(".additionalDirectories"))
|
|
120
103
|
? " Extra directories ride the top-level body.additionalDirectories instead."
|
|
@@ -123,8 +106,8 @@ export function taskSettingsKeyIssue(raw) {
|
|
|
123
106
|
message: `settings: unrecognized key(s) — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
|
|
124
107
|
`${parts.join("; ")}.${dirHint} ` +
|
|
125
108
|
`Accepted: settings.{${[...TASK_SETTINGS_KEYS].join(",")}} and settings.permissions.{${[...TASK_SETTINGS_PERMISSION_KEYS].join(",")}}`,
|
|
126
|
-
unknownKeys: unknown.slice(0,
|
|
127
|
-
unsupportedKeys: unsupported.slice(0,
|
|
109
|
+
unknownKeys: unknown.slice(0, MAX_KEY_ECHO),
|
|
110
|
+
unsupportedKeys: unsupported.slice(0, MAX_KEY_ECHO),
|
|
128
111
|
};
|
|
129
112
|
}
|
|
130
113
|
export function parseTaskSettings(raw) {
|
|
@@ -178,8 +161,13 @@ export function parseTaskSettings(raw) {
|
|
|
178
161
|
out.ultracode = true;
|
|
179
162
|
if (hooksConfig)
|
|
180
163
|
out.hooks = hooksConfig;
|
|
181
|
-
const
|
|
182
|
-
|
|
164
|
+
const fraction = s.skillListingBudgetFraction;
|
|
165
|
+
if (typeof fraction === "number" && Number.isFinite(fraction) && fraction > 0 && fraction <= 1)
|
|
166
|
+
out.skillListingBudgetFraction = fraction;
|
|
167
|
+
const maxDesc = s.skillListingMaxDescChars;
|
|
168
|
+
if (typeof maxDesc === "number" && Number.isSafeInteger(maxDesc) && maxDesc > 0)
|
|
169
|
+
out.skillListingMaxDescChars = maxDesc;
|
|
170
|
+
return { settings: Object.keys(out).length > 0 ? out : undefined, deferred };
|
|
183
171
|
}
|
|
184
172
|
export function hasConstitutionAnchors(text) {
|
|
185
173
|
return typeof text === "string" && text.includes(CYBER_RISK) && text.includes(URL_SAFETY) && text.includes(HARNESS_SECTION_ANCHOR);
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
|
-
import { type AskRequest, type AskOutcome, type RuleOffer } from "@sema-agent/core";
|
|
2
|
+
import { type AskRequest, type AskOutcome, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
|
|
3
3
|
import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
|
|
4
4
|
import { type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
|
|
5
5
|
import { type ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
|
|
@@ -169,6 +169,27 @@ export interface ToolApprovalFrame {
|
|
|
169
169
|
* `GateDisposition.denied.cause`(`tool_end.gate` / `permissionDenied.gate`),ask 面自此没有这一位。
|
|
170
170
|
*/
|
|
171
171
|
ruleStoreUnreadable?: string;
|
|
172
|
+
/**
|
|
173
|
+
* **[ref]**(core 7.19.0;**ADDITIVE**,`"tool_approval"` only)—— 这只**根外读** ask 的**清障目录**:
|
|
174
|
+
* 把 `dir` 加进本会话的读目录(壳侧既有的 `/add-dir` 口),同一条调用就不会再问。
|
|
175
|
+
*
|
|
176
|
+
* 🔴 **在场才读,永远不读缺席**。缺席不是「无计可施」:它同时覆盖敏感路径 deny 行(按模式判,加任何
|
|
177
|
+
* 根都不动它)、读边界压根读不懂的命令(heredoc / 换行 / 命令替换 / 参数当程序)、没展开的 glob、
|
|
178
|
+
* 递归遍历、以及每一只**不是**读边界提的 ask —— 7.18.0 及以前的**每**一只 ask 都是这一形。
|
|
179
|
+
*
|
|
180
|
+
* 🔴 **原样回传**(core 契约 `shell.read_boundary.grant_candidate_is_the_grant`):`dir` 是绝对、已做词法
|
|
181
|
+
* 折叠、用读边界自己的拼法写的串;**显示的串就是要加的串**。壳可以为了好看渲一个缩短形,回传必须是
|
|
182
|
+
* 本键里的原串。⇒ 本仓的姿势是「整只逐字带,或整只丢」,**绝不截**(见 `MAX_READ_ROOT_CANDIDATE_DIR`)。
|
|
183
|
+
*
|
|
184
|
+
* 🔴 **echo-only,零判读**:server 不从命令文本重推目录,也不复核它真不真能清掉这只 ask —— core 已经
|
|
185
|
+
* 把提议的根放回读边界又走了一遍才递出来,下游再判一遍就是同一事实的第二个判官(与 `origin` /
|
|
186
|
+
* `ruleStoreUnreadable` / `denialLimitFallback` 三条顶注的同一条禁令)。
|
|
187
|
+
*
|
|
188
|
+
* 🔴 **只此一面**:core 的负控逐字写着「the durable park row has no twin of this seat in this version」——
|
|
189
|
+
* 一只 PARKED 的根外读 ask 渲的仍是单独那张确认卡。所以 `card_json` / 耐久面**没有**这一座,本仓也不
|
|
190
|
+
* 自铸一个(自铸 = 同一事实的第二个源,而且那个源会在 park 兑现时说谎)。
|
|
191
|
+
*/
|
|
192
|
+
readRootCandidate?: ReadRootGrantCandidate;
|
|
172
193
|
/**
|
|
173
194
|
* **E-14**([ref]② Trojan Source 族;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**工具输入**
|
|
174
195
|
* (`AskRequest.args`)里含 bidi 控制符(LRM/RLM、嵌入/覆写、隔离符;字符类属主 `text-bidi.ts`)。
|
|
@@ -858,9 +879,20 @@ export declare class ToolApprovalCoordinator {
|
|
|
858
879
|
* 残余 = 那类行的归因会记成 `routing_failure`(其实是政策 deny),归因精化登记为候件。
|
|
859
880
|
*/
|
|
860
881
|
private unattendedOutcome;
|
|
882
|
+
/** 政策**读**口(不是终局铸口)—— 回答「这台部署把无人可答翻成 park 吗」。
|
|
883
|
+
*
|
|
884
|
+
* 🔴 与 {@link unattendedOutcome} 分家是 S-187 的一半:此前两件事共用一只方法,于是墓碑腿那次
|
|
885
|
+
* 纯粹的**政策查询**(`unattendedOutcome() !== false`)会和同一只 ask 的真终局各记一条遥测 ——
|
|
886
|
+
* 「一只 ask 一条线」当场不成立。读口不落日志,铸口落;谁也不兼职。 */
|
|
887
|
+
private get unattendedRoutesToPark();
|
|
861
888
|
/** {@link unattendedOutcome} 的**公开**口:`windowZero` 的 immediate-unavailable 闭包长在装配层
|
|
862
|
-
* (`http/routes/tasks.ts` 的 sync 腿),它必须与协调器内五臂读**同一个**旋钮值,而不是各算一遍。
|
|
863
|
-
|
|
889
|
+
* (`http/routes/tasks.ts` 的 sync 腿),它必须与协调器内五臂读**同一个**旋钮值,而不是各算一遍。
|
|
890
|
+
* 出处随调用方给(那条腿知道自己是哪一只 ask)。 */
|
|
891
|
+
unattendedAskOutcome(site: {
|
|
892
|
+
taskId?: string;
|
|
893
|
+
owner?: string | null;
|
|
894
|
+
toolName?: string;
|
|
895
|
+
}): "unavailable" | false;
|
|
864
896
|
/**
|
|
865
897
|
* [ref] 车3 刀 3b —— [ref] §3.3 / 设计稿 §0 X-2 的**写侧准入门**。
|
|
866
898
|
*
|
package/dist/tool-approval.js
CHANGED
|
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
|
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
9
9
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
10
10
|
import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
11
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
11
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
12
12
|
import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
13
13
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
14
14
|
const defaultLogger = createLogger();
|
|
@@ -300,11 +300,29 @@ export class ToolApprovalCoordinator {
|
|
|
300
300
|
this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
|
|
301
301
|
this.askAudit = opts?.askAudit;
|
|
302
302
|
}
|
|
303
|
-
unattendedOutcome() {
|
|
304
|
-
|
|
303
|
+
unattendedOutcome(site) {
|
|
304
|
+
const outcome = this.unattendedPolicy === "deny" ? false : "unavailable";
|
|
305
|
+
defaultLogger.warn("approver_unavailable", {
|
|
306
|
+
arm: site.arm,
|
|
307
|
+
policy: this.unattendedPolicy,
|
|
308
|
+
outcome: outcome === false ? "deny" : "unavailable",
|
|
309
|
+
owner: site.owner ?? null,
|
|
310
|
+
taskId: site.taskId ?? null,
|
|
311
|
+
toolName: site.toolName ?? null,
|
|
312
|
+
reachability: site.reachability ?? null,
|
|
313
|
+
});
|
|
314
|
+
return outcome;
|
|
315
|
+
}
|
|
316
|
+
get unattendedRoutesToPark() {
|
|
317
|
+
return this.unattendedPolicy !== "deny";
|
|
305
318
|
}
|
|
306
|
-
unattendedAskOutcome() {
|
|
307
|
-
return this.unattendedOutcome(
|
|
319
|
+
unattendedAskOutcome(site) {
|
|
320
|
+
return this.unattendedOutcome({
|
|
321
|
+
arm: "window_zero",
|
|
322
|
+
...(site.taskId !== undefined ? { taskId: site.taskId } : {}),
|
|
323
|
+
...(site.owner !== undefined && site.owner !== null ? { owner: site.owner } : {}),
|
|
324
|
+
...(site.toolName !== undefined ? { toolName: site.toolName } : {}),
|
|
325
|
+
});
|
|
308
326
|
}
|
|
309
327
|
admit(taskId, owner) {
|
|
310
328
|
if (!this.askStore)
|
|
@@ -758,7 +776,7 @@ export class ToolApprovalCoordinator {
|
|
|
758
776
|
ask = async (req, signal) => {
|
|
759
777
|
const ctx = this.als.getStore();
|
|
760
778
|
if (!ctx)
|
|
761
|
-
return this.unattendedOutcome();
|
|
779
|
+
return this.unattendedOutcome({ arm: "no_run_context", toolName: req.toolName });
|
|
762
780
|
return this.askBroadcast({
|
|
763
781
|
taskId: ctx.taskId,
|
|
764
782
|
owner: ctx.owner,
|
|
@@ -786,11 +804,9 @@ export class ToolApprovalCoordinator {
|
|
|
786
804
|
const aliveCtxs = ctxs.filter((c) => !c.abortSignal?.aborted);
|
|
787
805
|
const reachability = aliveCtxs.length === 0 ? "unreached" : "reachable";
|
|
788
806
|
if (reachability === "unreached") {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!this.askStore)
|
|
793
|
-
return unattended;
|
|
807
|
+
if (!this.unattendedRoutesToPark || !this.askStore) {
|
|
808
|
+
return this.unattendedOutcome({ arm: "no_live_target", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
809
|
+
}
|
|
794
810
|
}
|
|
795
811
|
const primary = aliveCtxs[0];
|
|
796
812
|
const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
|
|
@@ -809,7 +825,7 @@ export class ToolApprovalCoordinator {
|
|
|
809
825
|
admitMaxPerTask: this.admitMaxPerTask,
|
|
810
826
|
admitMaxPerOwner: this.admitMaxPerOwner,
|
|
811
827
|
});
|
|
812
|
-
return this.unattendedOutcome();
|
|
828
|
+
return this.unattendedOutcome({ arm: "admission_exhausted", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
813
829
|
}
|
|
814
830
|
const id = uuidv7();
|
|
815
831
|
const child = isChildAsk(req);
|
|
@@ -848,6 +864,7 @@ export class ToolApprovalCoordinator {
|
|
|
848
864
|
...((a) => (a !== undefined ? { ruleOffersAbsence: a } : {}))(readRuleOffersAbsence(req)),
|
|
849
865
|
...((f) => (f !== undefined ? { denialLimitFallback: f } : {}))(readDenialLimitFallback(req)),
|
|
850
866
|
...(typeof req.ruleStoreUnreadable === "string" && req.ruleStoreUnreadable.length > 0 ? { ruleStoreUnreadable: req.ruleStoreUnreadable } : {}),
|
|
867
|
+
...((g) => (g !== undefined ? { readRootCandidate: g } : {}))(readReadRootCandidate(req)),
|
|
851
868
|
...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
|
|
852
869
|
...((c) => (c !== undefined ? { ruleEvidence: c } : {}))(readRuleEvidence(req)),
|
|
853
870
|
...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
|
|
@@ -904,7 +921,9 @@ export class ToolApprovalCoordinator {
|
|
|
904
921
|
if (existingOrCreated.row.state !== "STREAM_PENDING") {
|
|
905
922
|
releaseAdmission();
|
|
906
923
|
const replayed = replayTerminalAskRow(existingOrCreated.row);
|
|
907
|
-
return replayed === "unavailable"
|
|
924
|
+
return replayed === "unavailable"
|
|
925
|
+
? this.unattendedOutcome({ arm: "replayed_parked", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability })
|
|
926
|
+
: replayed;
|
|
908
927
|
}
|
|
909
928
|
const persistedEnvelope = ApprovalCardEnvelopeSchema.safeParse(existingOrCreated.row.cardJson);
|
|
910
929
|
const maxMintableWindowMs = Math.max(effectiveWindowMs, this.unreachedTtlMs);
|
|
@@ -985,7 +1004,7 @@ export class ToolApprovalCoordinator {
|
|
|
985
1004
|
voidAbandonedRow(lateEnsuredAskId);
|
|
986
1005
|
if (reachability === "unreached") {
|
|
987
1006
|
releaseAdmission();
|
|
988
|
-
return this.unattendedOutcome();
|
|
1007
|
+
return this.unattendedOutcome({ arm: "unreached_no_row", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
989
1008
|
}
|
|
990
1009
|
}
|
|
991
1010
|
}
|
|
@@ -1014,8 +1033,11 @@ export class ToolApprovalCoordinator {
|
|
|
1014
1033
|
return false;
|
|
1015
1034
|
};
|
|
1016
1035
|
const shapeOutcome = (settled) => {
|
|
1017
|
-
if (windowRouteUnavailable)
|
|
1018
|
-
return this.unattendedOutcome(
|
|
1036
|
+
if (windowRouteUnavailable) {
|
|
1037
|
+
return this.unattendedOutcome({ arm: "window_expired", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability }) === false
|
|
1038
|
+
? shapeDeny()
|
|
1039
|
+
: "unavailable";
|
|
1040
|
+
}
|
|
1019
1041
|
if (settled.allowed) {
|
|
1020
1042
|
return settled.updatedInput !== undefined ? { allow: true, updatedInput: settled.updatedInput } : true;
|
|
1021
1043
|
}
|
|
@@ -1032,7 +1054,7 @@ export class ToolApprovalCoordinator {
|
|
|
1032
1054
|
for (const [sig, fn] of abortListeners)
|
|
1033
1055
|
sig.removeEventListener("abort", fn);
|
|
1034
1056
|
this.pending.delete(id);
|
|
1035
|
-
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.
|
|
1057
|
+
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedRoutesToPark) {
|
|
1036
1058
|
this.recordParkTombstone(wireApprovalId, { kind: "park", askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1037
1059
|
parkRouted = true;
|
|
1038
1060
|
}
|
|
@@ -1456,11 +1478,11 @@ export class ToolApprovalCoordinator {
|
|
|
1456
1478
|
.catch(() => undefined);
|
|
1457
1479
|
}
|
|
1458
1480
|
if (emitFailed)
|
|
1459
|
-
return this.unattendedOutcome();
|
|
1481
|
+
return this.unattendedOutcome({ arm: "emit_failed", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1460
1482
|
if (windowRouteUnavailable)
|
|
1461
1483
|
return shapeOutcome(settled);
|
|
1462
1484
|
if (!emitSettled && lastOutcome === "expired")
|
|
1463
|
-
return this.unattendedOutcome();
|
|
1485
|
+
return this.unattendedOutcome({ arm: "emit_unsettled", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1464
1486
|
return shapeOutcome(settled);
|
|
1465
1487
|
}
|
|
1466
1488
|
respond(id, principal, body, httpReq) {
|
|
@@ -43,7 +43,7 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
|
|
|
43
43
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
44
44
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
45
45
|
type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
|
|
46
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable";
|
|
46
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "readRootCandidate";
|
|
47
47
|
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
|
|
48
48
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
49
49
|
type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
|
|
@@ -121,8 +121,8 @@ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "seve
|
|
|
121
121
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
|
|
122
122
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
123
123
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
124
|
-
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" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested";
|
|
125
|
-
type TaskSpecUnwired = "excludeAllTools" | "
|
|
124
|
+
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" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars";
|
|
125
|
+
type TaskSpecUnwired = "excludeAllTools" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
|
|
126
126
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
127
127
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
128
128
|
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";
|
|
@@ -2,7 +2,7 @@ import { terminalProjection } from "@sema-agent/core";
|
|
|
2
2
|
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
3
3
|
import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
|
|
4
4
|
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
5
|
-
import { isModelSegmentBoundary } from "./model-segment.js";
|
|
5
|
+
import { isModelSegmentBoundary, reasoningDeltaBytes, reasoningSegmentEventId } from "./model-segment.js";
|
|
6
6
|
export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
7
7
|
let seq = startSeq;
|
|
8
8
|
let chain = Promise.resolve();
|
|
@@ -19,6 +19,7 @@ export function createLedgerSink(opts) {
|
|
|
19
19
|
let text = "";
|
|
20
20
|
let reasoning = "";
|
|
21
21
|
let textEventId;
|
|
22
|
+
let reasoningEventId;
|
|
22
23
|
const beat = (kind, statusData) => {
|
|
23
24
|
try {
|
|
24
25
|
opts.onActivity?.(kind, statusData);
|
|
@@ -34,8 +35,9 @@ export function createLedgerSink(opts) {
|
|
|
34
35
|
};
|
|
35
36
|
const flush = async () => {
|
|
36
37
|
if (reasoning) {
|
|
37
|
-
await append("reasoning", modelTextEventData({ text: reasoning }));
|
|
38
|
+
await append("reasoning", modelTextEventData({ text: reasoning, eventId: reasoningEventId }));
|
|
38
39
|
reasoning = "";
|
|
40
|
+
reasoningEventId = undefined;
|
|
39
41
|
}
|
|
40
42
|
if (text) {
|
|
41
43
|
await append("text", modelTextEventData({ text, eventId: textEventId }));
|
|
@@ -53,10 +55,15 @@ export function createLedgerSink(opts) {
|
|
|
53
55
|
textEventId ??= td.eventId;
|
|
54
56
|
break;
|
|
55
57
|
}
|
|
56
|
-
case "reasoning_delta":
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
case "reasoning_delta": {
|
|
59
|
+
const rd = ev;
|
|
60
|
+
const bytes = reasoningDeltaBytes(rd);
|
|
61
|
+
if (opts.persistThinking && bytes !== undefined) {
|
|
62
|
+
reasoning += bytes;
|
|
63
|
+
reasoningEventId ??= reasoningSegmentEventId(rd.eventId);
|
|
64
|
+
}
|
|
59
65
|
break;
|
|
66
|
+
}
|
|
60
67
|
case "tool_start":
|
|
61
68
|
await append("tool_start", toolStartEventData(ev));
|
|
62
69
|
break;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SegmentEndFields } from "./project.js";
|
|
1
2
|
/**
|
|
2
3
|
* 本帧是不是模型文本的段界。**四条消费腿共用这一只**:账本投影 switch(`trace/ledger-sink.ts`)、
|
|
3
4
|
* 顶层 live 流与转发子代腿(`http/routes/tasks.ts`)、子代 tail 面(`fleet/subagent-tail-bus.ts`)。
|
|
@@ -5,8 +6,10 @@
|
|
|
5
6
|
* (第三方 Runner 塞进来的野帧不该切走宿主的权威段)。
|
|
6
7
|
*/
|
|
7
8
|
export declare function isModelSegmentBoundary(type: string): boolean;
|
|
8
|
-
/** live 面的段末帧(`type` 即事件名;其余键由 `reasoningEndEventData` 白名单铸出)。
|
|
9
|
-
|
|
9
|
+
/** live 面的段末帧(`type` 即事件名;其余键由 `reasoningEndEventData` 白名单铸出)。
|
|
10
|
+
* 形是**声明**出来的、不是 `Record<string, unknown>`(S-334 门①):`eventId` 是本帧的续流游标,
|
|
11
|
+
* 型必须说得出口 —— live 腿的 `id:` 行取的正是它。 */
|
|
12
|
+
export type ReasoningEndFrame = SegmentEndFields & {
|
|
10
13
|
type: "reasoning_end";
|
|
11
14
|
};
|
|
12
15
|
/** 登记册**读到的全部字段**,逐个写出来(不是 `Record<string, unknown>`):`TaskEvent` 与转发腿的原始帧
|
|
@@ -48,6 +51,33 @@ export interface ReasoningSegments {
|
|
|
48
51
|
* 与账本腿 `LedgerSink.onDone → flush()` 同位同义 —— 段界表里没有终局词,终局走这扇门。 */
|
|
49
52
|
cut(match?: (ident: ReasoningStreamIdent) => boolean): ReasoningEndFrame[];
|
|
50
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* 一段推理的**段末事件 id** —— 唯一铸点,live 段末帧与账本 `reasoning` 行共用这一只。
|
|
56
|
+
*
|
|
57
|
+
* 🔴 **为什么段末帧不能沿用任何一块 delta 的 `eventId`**(S-334):SSE 的 `id:` 行是**续流游标**,
|
|
58
|
+
* 「每帧唯一」就是它的定义(消费端据它做**整帧幂等**:见过的 id 整帧丢);而本仓 live 腿的 `id:` 行
|
|
59
|
+
* 恰恰取自载荷的 `eventId`(`http/routes/tasks.ts` 的 `sseData`)。沿用段内某一块的锚 ⇒ 段末帧与那一块
|
|
60
|
+
* delta 顶着同一枚游标,消费端把**权威段**当重放帧丢弃 —— 本帧存在的唯一理由(整段脱敏后的权威副本)
|
|
61
|
+
* 当场结构性失效。「这段是从哪一帧开始攒的」不是游标要回答的问题,而「整段替换」这条消费律本来就
|
|
62
|
+
* 不需要段首锚:重连从**帧**续,不从段续。
|
|
63
|
+
*
|
|
64
|
+
* 形 = 那一枚锚 + 一个派生记号,**确定性**是它的全部要点:live 腿与账本腿各自看着同一条流、在同一个
|
|
65
|
+
* 段界上算出**同一枚**字符串,于是「同一份权威副本」在身份面也成立,而两条腿之间不需要任何传值通道
|
|
66
|
+
* ——bg / resume 两条账本腿上根本没有 live 登记册可问,靠传值就只能做成「detach 车道有、别的腿没有」
|
|
67
|
+
* 的两种账本行形。
|
|
68
|
+
*
|
|
69
|
+
* 输入 = 本段**第一块带锚**的 delta 的 `eventId`(不是「第一块」的:首块无锚不等于整段无锚,三条腿
|
|
70
|
+
* 统一用 `??=` 闩第一枚**有定义**的锚 —— 与 E18 的 `textEventId ??=` 逐字同律)。整段一块带锚的都没有
|
|
71
|
+
* ⇒ 段末 id 缺席(诚实的「无锚」,绝不合成占位 —— 与 `sseData` 的既有纪律同源:不可锚的位置不许
|
|
72
|
+
* 伪装成可锚)。
|
|
73
|
+
*/
|
|
74
|
+
export declare function reasoningSegmentEventId(anchor: unknown): string | undefined;
|
|
75
|
+
/** 一块 `reasoning_delta` 对段的**贡献字节** —— 非空串才算。账本腿与三条 live 腿共用这一只:空/坏形
|
|
76
|
+
* 增量一个字节都不贡献,既不开段也不参与闩锚 ⇒ 两面结构上不可能把锚闩在不同的帧上(`reasoning_end`
|
|
77
|
+
* 与账本 `reasoning` 行的正文早就逐字节相等,身份面随 S-334 收进同一条规则)。 */
|
|
78
|
+
export declare function reasoningDeltaBytes(ev: {
|
|
79
|
+
delta?: unknown;
|
|
80
|
+
}): string | undefined;
|
|
51
81
|
/** 「这条流属于这些子代 id 之一」—— 子代终态收段的匹配器(`task_id` 与 `sessionId` 是同一个子代的两个
|
|
52
82
|
* id 域,取并集;`undefined` 成员自动忽略)。腿上共用这一只,免得各写一遍 id 域的并集。 */
|
|
53
83
|
export declare function reasoningStreamOfTask(...ids: ReadonlyArray<string | undefined>): (ident: ReasoningStreamIdent) => boolean;
|
|
@@ -7,6 +7,13 @@ const BOUNDARY = new Set([
|
|
|
7
7
|
export function isModelSegmentBoundary(type) {
|
|
8
8
|
return BOUNDARY.has(type);
|
|
9
9
|
}
|
|
10
|
+
const REASONING_SEGMENT_ID_MARK = "~reasoning_end";
|
|
11
|
+
export function reasoningSegmentEventId(anchor) {
|
|
12
|
+
return typeof anchor === "string" && anchor.length > 0 ? `${anchor}${REASONING_SEGMENT_ID_MARK}` : undefined;
|
|
13
|
+
}
|
|
14
|
+
export function reasoningDeltaBytes(ev) {
|
|
15
|
+
return typeof ev.delta === "string" && ev.delta !== "" ? ev.delta : undefined;
|
|
16
|
+
}
|
|
10
17
|
export function reasoningStreamOfTask(...ids) {
|
|
11
18
|
const set = new Set(ids.filter((v) => typeof v === "string" && v !== ""));
|
|
12
19
|
return (ident) => (ident.sourceTaskId !== undefined && set.has(ident.sourceTaskId)) || (ident.bgAgentId !== undefined && set.has(ident.bgAgentId));
|
|
@@ -25,21 +32,24 @@ export function createReasoningSegments() {
|
|
|
25
32
|
if (seg === undefined)
|
|
26
33
|
return [];
|
|
27
34
|
open.delete(key);
|
|
28
|
-
const data = reasoningEndEventData({ ...seg.ident, eventId: seg.
|
|
35
|
+
const data = reasoningEndEventData({ ...seg.ident, eventId: seg.segmentEventId, content: seg.buf });
|
|
29
36
|
return data === null ? [] : [{ type: "reasoning_end", ...data }];
|
|
30
37
|
};
|
|
31
38
|
return {
|
|
32
39
|
onEvent: (ev) => {
|
|
33
40
|
if (ev.type === "reasoning_delta") {
|
|
34
|
-
|
|
41
|
+
const bytes = reasoningDeltaBytes(ev);
|
|
42
|
+
if (bytes === undefined)
|
|
35
43
|
return [];
|
|
36
44
|
const ident = identOf(ev);
|
|
37
45
|
const key = streamKeyOf(ident);
|
|
38
46
|
const seg = open.get(key);
|
|
39
47
|
if (seg === undefined)
|
|
40
|
-
open.set(key, { ident,
|
|
41
|
-
else
|
|
42
|
-
seg.buf +=
|
|
48
|
+
open.set(key, { ident, segmentEventId: reasoningSegmentEventId(ev.eventId), buf: bytes });
|
|
49
|
+
else {
|
|
50
|
+
seg.buf += bytes;
|
|
51
|
+
seg.segmentEventId ??= reasoningSegmentEventId(ev.eventId);
|
|
52
|
+
}
|
|
43
53
|
return [];
|
|
44
54
|
}
|
|
45
55
|
return isModelSegmentBoundary(ev.type ?? "") ? cutKey(streamKeyOf(identOf(ev))) : [];
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -192,8 +192,42 @@ export declare function screenedStoredEventData(type: string, data: unknown): un
|
|
|
192
192
|
* 此前三处各自逐字透传 —— 同一份文本毫秒后在 `done.result.result` 上已脱,live 面却在凭据生成瞬间明文(B-114)。
|
|
193
193
|
* `text_delta` 仍逐字:跨 chunk 的凭据无法就地判,流式脱敏是独立设计件(S-270)。
|
|
194
194
|
* 坏形(content 非串/空串)⇒ null(丢帧,不铸「权威空段」:core 契约明言空段不发本帧,消费端收到空段会清掉已攒 delta)。
|
|
195
|
-
*
|
|
196
|
-
|
|
195
|
+
* 身份**四键**随行(live 现状带全四键,消费端可能已依赖);值经 {@link identityKey} —— 非空 string 才上 wire
|
|
196
|
+
* (S-334 起不再逐字透传:坏形身份键会在 live 写口上铸出假游标)。 */
|
|
197
|
+
export declare function textEndEventData(ev: Record<string, unknown>): SegmentEndFields | null;
|
|
198
|
+
/**
|
|
199
|
+
* live 面的**身份四键**(core `TaskEventIdentity` 全集)—— 一个属主,两处共用({@link segmentEndFields}
|
|
200
|
+
* 与 sync 腿白名单 catch-all 的 `ident`;此前是两份手抄)。与 durable 面的 {@link identityFields} 的差别
|
|
201
|
+
* **只剩键集**:那边两键(durable builder 对另两键的既定排除),这边四键(live 现状,消费端已依赖);
|
|
202
|
+
* **值判据是同一条** —— 两只都问 {@link identityKey}(非空 `string` 才上 wire,绝不强转)。
|
|
203
|
+
*
|
|
204
|
+
* 🔴 **型必须说得出口**(S-334):`eventId` 是 live 腿 SSE `id:` 行的**唯一**来源,而 `id:` 行是续流游标
|
|
205
|
+
* ——一个连值的型都说不出的键不许铸成游标。非串/空串 ⇒ 键缺席(与「无锚不发 `id:` 行」同一条纪律:
|
|
206
|
+
* 不可锚的位置绝不伪装成可锚)。转发子代腿的帧是 UNTRUSTED RAW(§E1),这一道同时是它的形判门。
|
|
207
|
+
*
|
|
208
|
+
* 🔴 **`type` 而不是 `interface`**(它与 {@link SegmentEndFields} 两处都是,别改回去):TS 只给**类型别名**
|
|
209
|
+
* 隐式索引签名,interface 没有 ⇒ 写成 interface 的话,每一个把本形交给 `Record<string, unknown>` 形参
|
|
210
|
+
* (tail 总线帧、live 帧写口)的调用点都得手抄一次 `{ ...frame }` 展开。别名让「声明出来的形」原样流经
|
|
211
|
+
* 既有写口,零调用点噪声。
|
|
212
|
+
*/
|
|
213
|
+
export type LiveEventIdentity = {
|
|
214
|
+
eventId?: string;
|
|
215
|
+
parentToolCallId?: string;
|
|
216
|
+
sourceTaskId?: string;
|
|
217
|
+
bgAgentId?: string;
|
|
218
|
+
};
|
|
219
|
+
/** 段末帧(`text_end` / `reasoning_end`)的载荷**形**(两只帧一个字节不差 —— 一个 wire 概念一份形,
|
|
220
|
+
* 消费端的读法只学一次;差别只有各自铸点的空段判据)。 */
|
|
221
|
+
export type SegmentEndFields = LiveEventIdentity & {
|
|
222
|
+
content: string;
|
|
223
|
+
};
|
|
224
|
+
/** {@link LiveEventIdentity} 的铸点。[ref] 门①:条件展开的臂逐条是直写字面量(键名对扫描器可见)。 */
|
|
225
|
+
export declare function liveIdentityFields(ev: {
|
|
226
|
+
eventId?: unknown;
|
|
227
|
+
parentToolCallId?: unknown;
|
|
228
|
+
sourceTaskId?: unknown;
|
|
229
|
+
bgAgentId?: unknown;
|
|
230
|
+
}): LiveEventIdentity;
|
|
197
231
|
/**
|
|
198
232
|
* `reasoning_end` 段末帧的**唯一**铸点(三条 live 腿共用:top stream / 转发子代腿 / 子代 tail 面)——
|
|
199
233
|
* `text_end` 的推理面孪生({@link textEndEventData}),形逐字同(共用 {@link segmentEndFields})。
|
|
@@ -210,7 +244,7 @@ export declare function textEndEventData(ev: Record<string, unknown>): Record<st
|
|
|
210
244
|
* 「整段替换已攒的 delta」—— 发一枚空白权威段 = 让消费端把已渲染的思考清成空白。坏形(`content` 非串)
|
|
211
245
|
* 同样丢帧,理由与 `text_end` 逐字同源。
|
|
212
246
|
*/
|
|
213
|
-
export declare function reasoningEndEventData(ev: Record<string, unknown>):
|
|
247
|
+
export declare function reasoningEndEventData(ev: Record<string, unknown>): SegmentEndFields | null;
|
|
214
248
|
/** The durable `tool_end` payload (shared by both append sites). E1: the model-facing `output` is UNTRUSTED RAW
|
|
215
249
|
* from core (file/shell/API bodies) — core only SIZE-bounds it (`truncated`), REDACTION is ours (same contract as
|
|
216
250
|
* `tool_start.args`); never re-fed to a model. E2: identity. Absent `output` ⇒ the tool produced no content body.
|
package/dist/trace/project.js
CHANGED
|
@@ -78,10 +78,13 @@ export function aggregateModelUsage(events) {
|
|
|
78
78
|
}
|
|
79
79
|
function identityFields(src) {
|
|
80
80
|
return {
|
|
81
|
-
...(src.eventId ? { eventId:
|
|
82
|
-
...(src.parentToolCallId ? { parentToolCallId:
|
|
81
|
+
...(identityKey(src.eventId) !== undefined ? { eventId: identityKey(src.eventId) } : {}),
|
|
82
|
+
...(identityKey(src.parentToolCallId) !== undefined ? { parentToolCallId: identityKey(src.parentToolCallId) } : {}),
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
function identityKey(v) {
|
|
86
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
87
|
+
}
|
|
85
88
|
export function modelTextEventData(ev) {
|
|
86
89
|
return { text: ev.text, ...(ev.eventId ? { eventId: ev.eventId } : {}) };
|
|
87
90
|
}
|
|
@@ -192,15 +195,17 @@ export function textEndEventData(ev) {
|
|
|
192
195
|
return null;
|
|
193
196
|
return segmentEndFields(ev.content, ev);
|
|
194
197
|
}
|
|
195
|
-
function
|
|
198
|
+
export function liveIdentityFields(ev) {
|
|
196
199
|
return {
|
|
197
|
-
|
|
198
|
-
...(ev.
|
|
199
|
-
...(ev.
|
|
200
|
-
...(ev.
|
|
201
|
-
...(ev.bgAgentId !== undefined ? { bgAgentId: ev.bgAgentId } : {}),
|
|
200
|
+
...(identityKey(ev.eventId) !== undefined ? { eventId: identityKey(ev.eventId) } : {}),
|
|
201
|
+
...(identityKey(ev.parentToolCallId) !== undefined ? { parentToolCallId: identityKey(ev.parentToolCallId) } : {}),
|
|
202
|
+
...(identityKey(ev.sourceTaskId) !== undefined ? { sourceTaskId: identityKey(ev.sourceTaskId) } : {}),
|
|
203
|
+
...(identityKey(ev.bgAgentId) !== undefined ? { bgAgentId: identityKey(ev.bgAgentId) } : {}),
|
|
202
204
|
};
|
|
203
205
|
}
|
|
206
|
+
function segmentEndFields(content, ev) {
|
|
207
|
+
return { content: redactModelText(content), ...liveIdentityFields(ev) };
|
|
208
|
+
}
|
|
204
209
|
export function reasoningEndEventData(ev) {
|
|
205
210
|
if (typeof ev.content !== "string" || ev.content.trim().length === 0)
|
|
206
211
|
return null;
|
|
@@ -593,8 +598,7 @@ export function compactedEventData(ev) {
|
|
|
593
598
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
594
599
|
})();
|
|
595
600
|
return {
|
|
596
|
-
...(ev
|
|
597
|
-
...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
|
|
601
|
+
...identityFields(ev),
|
|
598
602
|
...(typeof ev.trigger === "string" ? { trigger: ev.trigger } : {}),
|
|
599
603
|
...(num(ev.tokensBefore) !== undefined ? { tokensBefore: num(ev.tokensBefore) } : {}),
|
|
600
604
|
...(ev.preserved_segment?.firstKeptEntryId ? { preserved_segment: { firstKeptEntryId: ev.preserved_segment.firstKeptEntryId } } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.78.
|
|
3
|
+
"version": "7.78.1",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "7.
|
|
57
|
+
"@sema-agent/core": "7.19.0",
|
|
58
58
|
"@sema-agent/settings-schema": "2.1.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"sharp": "^0.35.3"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@sema-agent/sdk": "9.
|
|
72
|
+
"@sema-agent/sdk": "9.5.0",
|
|
73
73
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
74
74
|
"@types/node": "22.10.2",
|
|
75
75
|
"@types/pg": "^8.20.0",
|