@sema-agent/server 3.8.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -649,6 +649,12 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
649
649
|
// 提示行。cwd 与已展示的工具 args 同信任级 → 共享白名单构造器(redactSecrets + bound)。
|
|
650
650
|
res.write(`data: ${JSON.stringify({ type: "workspace_changed", ...workspaceChangedEventData(ev) })}\n\n`);
|
|
651
651
|
}
|
|
652
|
+
else if (ev.type === "compaction_outcome") {
|
|
653
|
+
// core 2.5.0([2105]② 披露修):压缩非 compacted 结局上流帧——白名单投影:outcome/trigger
|
|
654
|
+
// 闭枚举 verbatim;reason 是引擎说明文本(可能引用配置值)→ redactSecrets;identity 两键同款。
|
|
655
|
+
const e = ev;
|
|
656
|
+
res.write(`data: ${JSON.stringify({ type: "compaction_outcome", outcome: e.outcome, trigger: e.trigger, ...(e.reason !== undefined ? { reason: redactSecrets(e.reason) } : {}), ...(e.eventId !== undefined ? { eventId: e.eventId } : {}), ...(e.parentToolCallId !== undefined ? { parentToolCallId: e.parentToolCallId } : {}) })}\n\n`);
|
|
657
|
+
}
|
|
652
658
|
else if (ev.type === "text_delta" || ev.type === "turn_end" || ev.type === "message_committed" || ev.type === "context_usage") {
|
|
653
659
|
// 裸 catch-all 白名单化收官(probe-wire-1 表征钉的根治半场):最后四个已知臂离开
|
|
654
660
|
// `JSON.stringify(ev)`。**零字节变化**白名单——已知键(含 E2 四 identity 键:live 现状
|
|
@@ -109,12 +109,46 @@ export function withWorktreeIsolation(baseFactory, opts) {
|
|
|
109
109
|
// down (core's hasDestroy() returns false for a plain base env). Behaviour identical to no wrapper.
|
|
110
110
|
return baseFactory(ctx);
|
|
111
111
|
}
|
|
112
|
+
// S3([2113] core 裁定的 server 半场):keep-if-changed 需要在保留分支跑 inner teardown(LSP evict
|
|
113
|
+
// 链等)而不跑 `git worktree remove` —— core addWorktree 把两者焊成一个复合 destroy 拆不开,所以在
|
|
114
|
+
// rootEnvAt 铸造点捕获 inner env 的**原** destroy 引用(per-mint 变量;core 稍后才把复合 destroy
|
|
115
|
+
// 装回同一对象上)。
|
|
116
|
+
let innerDestroy;
|
|
112
117
|
const { env, worktreeDir } = await serialize(() => addWorktree(opts.baseEnvForGit, {
|
|
113
118
|
repoRoot,
|
|
114
119
|
sessionId: ctx.sessionId, // core sanitizes this into the worktree dir name (human-readable orphan diag)
|
|
115
|
-
rootEnvAt:
|
|
120
|
+
rootEnvAt: async (dir) => {
|
|
121
|
+
const inner = await opts.rootEnvAt(dir);
|
|
122
|
+
const d = inner.destroy;
|
|
123
|
+
if (typeof d === "function")
|
|
124
|
+
innerDestroy = d.bind(inner);
|
|
125
|
+
return inner;
|
|
126
|
+
},
|
|
116
127
|
...(opts.commit ? { commit: opts.commit } : {}),
|
|
117
128
|
}));
|
|
129
|
+
// keep-if-changed 判定基点:mint 后立刻解析 worktree 的 HEAD(=detach 基点)。判定轴与 core Agent
|
|
130
|
+
// 车道逐字同款(subagent.js finish):dirty 文件 >0 ∥ base..HEAD commits >0 ⇒ changed。
|
|
131
|
+
let baseSha;
|
|
132
|
+
{
|
|
133
|
+
const r = await opts.baseEnvForGit.exec("git rev-parse HEAD", { cwd: worktreeDir });
|
|
134
|
+
if (r.ok && r.value.exitCode === 0)
|
|
135
|
+
baseSha = r.value.stdout.trim();
|
|
136
|
+
}
|
|
137
|
+
const isChanged = async () => {
|
|
138
|
+
// 判定失败 ⇒ 保守保留(Agent 车道 changed=true 缺省同款:删错=用户产出蒸发,留错=一个目录)。
|
|
139
|
+
const status = await opts.baseEnvForGit.exec("git status --porcelain", { cwd: worktreeDir });
|
|
140
|
+
if (!status.ok || status.value.exitCode !== 0)
|
|
141
|
+
return true;
|
|
142
|
+
const dirtyFiles = status.value.stdout.split("\n").filter((l) => l.trim() !== "").length;
|
|
143
|
+
if (dirtyFiles > 0)
|
|
144
|
+
return true;
|
|
145
|
+
if (baseSha === undefined)
|
|
146
|
+
return true;
|
|
147
|
+
const ahead = await opts.baseEnvForGit.exec(`git rev-list --count ${baseSha}..HEAD`, { cwd: worktreeDir });
|
|
148
|
+
if (!ahead.ok || ahead.value.exitCode !== 0)
|
|
149
|
+
return true;
|
|
150
|
+
return (parseInt(ahead.value.stdout.trim(), 10) || 0) > 0;
|
|
151
|
+
};
|
|
118
152
|
opts.logger?.info?.("worktree_isolation_minted", {
|
|
119
153
|
sessionId: ctx.sessionId,
|
|
120
154
|
...(ctx.taskId ? { taskId: ctx.taskId } : {}),
|
|
@@ -142,9 +176,30 @@ export function withWorktreeIsolation(baseFactory, opts) {
|
|
|
142
176
|
// already reached; double/concurrent destroy is a legal Runner shape). The first call always goes through
|
|
143
177
|
// coreDestroy (the inner env's own teardown must run).
|
|
144
178
|
let destroyedOnce = false;
|
|
179
|
+
let keptOnce = false;
|
|
145
180
|
env.destroy = () => serialize(async () => {
|
|
181
|
+
if (keptOnce)
|
|
182
|
+
return; // kept 是终态:二次 destroy 不得把保留的产出删掉
|
|
146
183
|
if (destroyedOnce && !(await residue()))
|
|
147
|
-
return; // idempotent repeat — done is done
|
|
184
|
+
return; // idempotent repeat — done is done(原语义:残留时放行重试)
|
|
185
|
+
// S3 keep-if-changed:有改动(或判不出来)⇒ 保留目录+registration 给 userland verify+merge,
|
|
186
|
+
// 只跑 inner teardown(LSP evict/host 收尾;rootEnvAt 铸的是 persistent 形,不 rm 目录),
|
|
187
|
+
// 并披露坐标 —— 不披露的保留=只是换了个姿势的蒸发。残留检查在本分支让位:目录在=目的。
|
|
188
|
+
if (await isChanged()) {
|
|
189
|
+
keptOnce = true;
|
|
190
|
+
try {
|
|
191
|
+
await innerDestroy?.();
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
opts.logger?.info?.("worktree_kept", {
|
|
195
|
+
worktreeDir,
|
|
196
|
+
sessionId: ctx.sessionId,
|
|
197
|
+
...(ctx.taskId ? { taskId: ctx.taskId } : {}),
|
|
198
|
+
note: "changes present — kept for verify/merge; remove with `git worktree remove` when done",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
148
203
|
await coreDestroy();
|
|
149
204
|
destroyedOnce = true;
|
|
150
205
|
if (!(await residue()))
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, TaskEvent } from "@sema-agent/core";
|
|
19
19
|
/** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。 */
|
|
20
20
|
type AssertAllKeysHandled<T extends never> = T;
|
|
21
|
-
type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId";
|
|
21
|
+
type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode";
|
|
22
22
|
type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected>>;
|
|
23
23
|
type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId";
|
|
24
24
|
type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "description" | "agentType" | "name" | "currentAction" | "currentTool" | "parentSessionId" | "startedAt" | "workflowRunId" | "progressTaskId" | "progressParentTaskId";
|
|
@@ -28,7 +28,7 @@ type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjec
|
|
|
28
28
|
type AskProjected = "toolName" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName";
|
|
29
29
|
type AskExcluded = "toolCallId" | "preview" | "principal" | "requiresRealApproval";
|
|
30
30
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
31
|
-
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";
|
|
31
|
+
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";
|
|
32
32
|
type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
|
|
33
33
|
export type CoreKeysetGuards = [_GuardNotification, _GuardBgNotif, _GuardRoster, _GuardAsk, _GuardTaskEvent];
|
|
34
34
|
export {};
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -185,6 +185,8 @@ export declare function taskNotificationEventData(ev: {
|
|
|
185
185
|
}>;
|
|
186
186
|
resumable?: boolean;
|
|
187
187
|
completionId?: string;
|
|
188
|
+
error?: string;
|
|
189
|
+
errorCode?: string;
|
|
188
190
|
};
|
|
189
191
|
eventId?: string;
|
|
190
192
|
parentToolCallId?: string;
|
|
@@ -250,6 +252,8 @@ export declare function brainStatusEventData(ev: {
|
|
|
250
252
|
parentToolCallId?: string;
|
|
251
253
|
}): Record<string, unknown>;
|
|
252
254
|
export declare function compactedEventData(ev: {
|
|
255
|
+
eventId?: string;
|
|
256
|
+
parentToolCallId?: string;
|
|
253
257
|
trigger?: string;
|
|
254
258
|
tokensBefore?: number;
|
|
255
259
|
preserved_segment?: {
|
package/dist/trace/project.js
CHANGED
|
@@ -198,6 +198,10 @@ export function taskNotificationEventData(ev) {
|
|
|
198
198
|
...(n.stoppedBy !== undefined ? { stoppedBy: n.stoppedBy } : {}),
|
|
199
199
|
// P1-3(core 1.452):完成幂等锚 verbatim(消费端跨通路去重;[1925] 一轮批)。
|
|
200
200
|
...(n.completionId !== undefined ? { completionId: n.completionId } : {}),
|
|
201
|
+
// RB-386(core 2.4.0 wire note,2.5.0 提货收编):bg agent 失败面归因两键——error 是引擎侧
|
|
202
|
+
// 失败说明(可能含网络地址等)→ redactSecrets;errorCode 是机器码 verbatim。缺席=非失败帧。
|
|
203
|
+
...(n.error !== undefined ? { error: redactSecrets(n.error) } : {}),
|
|
204
|
+
...(n.errorCode !== undefined ? { errorCode: n.errorCode } : {}),
|
|
201
205
|
// [1543]§二 server①:白名单滞后 core payload 四键补齐——
|
|
202
206
|
// source:external 通知来源标(caller 供 = UNTRUSTED)→ redactSecrets;
|
|
203
207
|
// exitCode:数值 verbatim(bash 腿);
|
|
@@ -312,6 +316,8 @@ export function compactedEventData(ev) {
|
|
|
312
316
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
313
317
|
})();
|
|
314
318
|
return {
|
|
319
|
+
...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
|
|
320
|
+
...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
|
|
315
321
|
// 🔴 2026-07-25:这三处此前**违反本函数注释自己写的那条不变量**(「畸形字段不得上线,与 1.72 同类」)——
|
|
316
322
|
// ① `trigger`/`tokensBefore` 是**裸赋值**:`tokensBefore` 为 `NaN` 时 `JSON.stringify` 把它变成 **`null`**,
|
|
317
323
|
// 于是压缩面板渲染成「压缩前 0 tokens」而不是「未知」;裸赋值还会留下 present-with-undefined 的键
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
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": "^2.
|
|
57
|
+
"@sema-agent/core": "^2.5.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.11.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|