@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.
@@ -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 unknown = [];
93
- const unsupported = [];
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
- scan(perms, "settings.permissions.", ACCEPTED_PERM, UNSUPPORTED_PERM);
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 ? [clause(unknown, "unknown (not a settings key here — check the spelling)")] : []),
117
- ...(unsupported.length > 0 ? [clause(unsupported, "unsupported (a CC settings key this service does not wire)")] : []),
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, MAX_SETTINGS_KEY_ECHO),
127
- unsupportedKeys: unsupported.slice(0, MAX_SETTINGS_KEY_ECHO),
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 hasApplied = out.permissions !== undefined || out.model !== undefined || out.outputStyle !== undefined || out.shellEnv !== undefined || out.ultracode !== undefined || out.hooks !== undefined;
182
- return { settings: hasApplied ? out : undefined, deferred };
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);
@@ -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
- unattendedAskOutcome(): "unavailable" | false;
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
  *
@@ -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
- return this.unattendedPolicy === "deny" ? false : "unavailable";
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
- const unattended = this.unattendedOutcome();
790
- if (unattended === false)
791
- return unattended;
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" ? this.unattendedOutcome() : replayed;
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() === false ? shapeDeny() : "unavailable";
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.unattendedOutcome() !== false) {
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" | "writeFace" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
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
- if (opts.persistThinking)
58
- reasoning += ev.delta;
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
- export type ReasoningEndFrame = Record<string, unknown> & {
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.eventId, content: seg.buf });
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
- if (typeof ev.delta !== "string" || ev.delta === "")
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, eventId: ev.eventId, buf: ev.delta });
41
- else
42
- seg.buf += ev.delta;
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))) : [];
@@ -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
- * 身份四键零字节变换(live 现状带全四键,消费端可能已依赖)。 */
196
- export declare function textEndEventData(ev: Record<string, unknown>): Record<string, unknown> | null;
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>): Record<string, unknown> | null;
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.
@@ -78,10 +78,13 @@ export function aggregateModelUsage(events) {
78
78
  }
79
79
  function identityFields(src) {
80
80
  return {
81
- ...(src.eventId ? { eventId: String(src.eventId) } : {}),
82
- ...(src.parentToolCallId ? { parentToolCallId: String(src.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 segmentEndFields(content, ev) {
198
+ export function liveIdentityFields(ev) {
196
199
  return {
197
- content: redactModelText(content),
198
- ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
199
- ...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
200
- ...(ev.sourceTaskId !== undefined ? { sourceTaskId: ev.sourceTaskId } : {}),
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.eventId !== undefined ? { eventId: ev.eventId } : {}),
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.0",
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.18.0",
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.4.0",
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",