@sema-agent/server 7.76.1 → 7.77.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.
- package/USAGE.md +47 -21
- package/dist/boot/engine-lease.d.ts +89 -11
- package/dist/boot/engine-lease.js +26 -11
- package/dist/boot/exit-last.d.ts +24 -0
- package/dist/boot/exit-last.js +46 -0
- package/dist/boot/shutdown.js +28 -4
- package/dist/config-catalog.js +3 -2
- package/dist/config-types.d.ts +23 -11
- package/dist/config.js +8 -8
- package/dist/fleet/subagent-tail-bus.d.ts +28 -2
- package/dist/fleet/subagent-tail-bus.js +24 -2
- package/dist/http/principal-gate.d.ts +43 -1
- package/dist/http/principal-gate.js +23 -1
- package/dist/http/routes/a2a-serve.js +2 -2
- package/dist/http/routes/admin-config-refresh.js +2 -2
- package/dist/http/routes/admin-drain.js +2 -2
- package/dist/http/routes/admin-memory-optout.js +2 -2
- package/dist/http/routes/adoption.js +2 -2
- package/dist/http/routes/approvals-assistant.js +4 -4
- package/dist/http/routes/attachments.js +2 -2
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.js +2 -2
- package/dist/http/routes/fleet.js +2 -2
- package/dist/http/routes/leader.js +2 -2
- package/dist/http/routes/memory-bundle.js +2 -2
- package/dist/http/routes/memory-compliance.js +2 -2
- package/dist/http/routes/memory-consolidation.js +2 -2
- package/dist/http/routes/memory-origin.js +2 -2
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/notify-wake.js +3 -3
- package/dist/http/routes/observability.js +2 -2
- package/dist/http/routes/retention-ops.js +2 -2
- package/dist/http/routes/run-memory-capture.js +2 -2
- package/dist/http/routes/runs.js +13 -13
- package/dist/http/routes/sessions.js +53 -12
- package/dist/http/routes/side-query.js +2 -2
- package/dist/http/routes/tasks.js +14 -10
- package/dist/http/routes/workflows.js +3 -3
- package/dist/http/server.js +10 -23
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/parent-watch.d.ts +32 -18
- package/dist/parent-watch.js +12 -10
- package/dist/parked-decide.js +1 -1
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +10 -0
- package/dist/plugins/memory-run-store.d.ts +6 -1
- package/dist/plugins/memory-run-store.js +10 -0
- package/dist/plugins/run-store-sql.d.ts +13 -2
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/store-contracts.d.ts +16 -0
- package/dist/runs.js +4 -10
- package/dist/tool-approval.js +2 -2
- package/dist/trace/ledger-sink.js +3 -14
- package/dist/trace/model-segment.d.ts +55 -0
- package/dist/trace/model-segment.js +58 -0
- package/dist/trace/project.d.ts +17 -0
- package/dist/trace/project.js +9 -1
- package/dist/trace/redact.d.ts +23 -1
- package/dist/trace/redact.js +49 -15
- package/package.json +1 -1
package/dist/parked-decide.js
CHANGED
|
@@ -208,7 +208,7 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
208
208
|
return "retry";
|
|
209
209
|
}
|
|
210
210
|
};
|
|
211
|
-
defaultSubagentTailBus.
|
|
211
|
+
defaultSubagentTailBus.forgetHandle(match.handle);
|
|
212
212
|
let receipt;
|
|
213
213
|
try {
|
|
214
214
|
receipt = await deps.reviveTool.execute(mintReviveArgs(row, deps.knownAgentTypes ?? new Set()), ctx);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult } from "./store-contracts.js";
|
|
3
|
+
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult, LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
|
|
5
5
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
6
6
|
/**
|
|
@@ -127,6 +127,11 @@ export declare class FileRunStore {
|
|
|
127
127
|
};
|
|
128
128
|
limit: number;
|
|
129
129
|
}): Promise<RunRecord[]>;
|
|
130
|
+
/**
|
|
131
|
+
* S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
|
|
132
|
+
* `createdAt DESC, taskId DESC`,与本店 {@link FileRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
|
|
133
|
+
*/
|
|
134
|
+
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
130
135
|
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
131
136
|
listSessions(opts: {
|
|
132
137
|
owner?: string;
|
|
@@ -443,6 +443,16 @@ export class FileRunStore {
|
|
|
443
443
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
444
444
|
return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
|
|
445
445
|
}
|
|
446
|
+
async latestRunForSession(sessionId) {
|
|
447
|
+
let latest;
|
|
448
|
+
for (const r of this.runs.values()) {
|
|
449
|
+
if (r.sessionId !== sessionId)
|
|
450
|
+
continue;
|
|
451
|
+
if (latest === undefined || r.createdAt.getTime() > latest.createdAt.getTime() || (r.createdAt.getTime() === latest.createdAt.getTime() && r.taskId > latest.taskId))
|
|
452
|
+
latest = r;
|
|
453
|
+
}
|
|
454
|
+
return latest === undefined ? undefined : { taskId: latest.taskId, owner: latest.owner, createdAt: latest.createdAt.toISOString() };
|
|
455
|
+
}
|
|
446
456
|
async listSessions(opts) {
|
|
447
457
|
const bySession = new Map();
|
|
448
458
|
for (const r of this.runs.values()) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult } from "./store-contracts.js";
|
|
3
|
+
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult, LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
5
5
|
/**
|
|
6
6
|
* [ref] P0 — the local lane's stand-in for the SQL twins' checkpoint-table JOIN. The file/memory run stores
|
|
@@ -76,6 +76,11 @@ export declare class MemoryRunStore {
|
|
|
76
76
|
};
|
|
77
77
|
limit: number;
|
|
78
78
|
}): Promise<RunRecord[]>;
|
|
79
|
+
/**
|
|
80
|
+
* S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
|
|
81
|
+
* `createdAt DESC, taskId DESC`,与本店 {@link MemoryRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
|
|
82
|
+
*/
|
|
83
|
+
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
79
84
|
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
80
85
|
listSessions(opts: {
|
|
81
86
|
owner?: string;
|
|
@@ -190,6 +190,16 @@ export class MemoryRunStore {
|
|
|
190
190
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
191
191
|
return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
|
|
192
192
|
}
|
|
193
|
+
async latestRunForSession(sessionId) {
|
|
194
|
+
let latest;
|
|
195
|
+
for (const r of this.runs.values()) {
|
|
196
|
+
if (r.sessionId !== sessionId)
|
|
197
|
+
continue;
|
|
198
|
+
if (latest === undefined || r.createdAt.getTime() > latest.createdAt.getTime() || (r.createdAt.getTime() === latest.createdAt.getTime() && r.taskId > latest.taskId))
|
|
199
|
+
latest = r;
|
|
200
|
+
}
|
|
201
|
+
return latest === undefined ? undefined : { taskId: latest.taskId, owner: latest.owner, createdAt: latest.createdAt.toISOString() };
|
|
202
|
+
}
|
|
193
203
|
async listSessions(opts) {
|
|
194
204
|
const bySession = new Map();
|
|
195
205
|
for (const r of this.runs.values()) {
|
|
@@ -2,10 +2,10 @@ import type { TaskStatus } from "@sema-agent/core";
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool } from "pg";
|
|
4
4
|
import { type UsageRow } from "../usage-analytics.js";
|
|
5
|
-
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult } from "./store-contracts.js";
|
|
5
|
+
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult, LatestRunRef } from "./store-contracts.js";
|
|
6
6
|
import { type SqlDriver } from "./sql-driver.js";
|
|
7
7
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
8
|
-
export type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
|
|
8
|
+
export type { RunRecord, SessionSummary, RunEvent, LatestRunRef } from "./store-contracts.js";
|
|
9
9
|
export declare class SqlRunStore {
|
|
10
10
|
protected readonly db: SqlDriver;
|
|
11
11
|
constructor(db: SqlDriver);
|
|
@@ -149,6 +149,17 @@ export declare class SqlRunStore {
|
|
|
149
149
|
};
|
|
150
150
|
limit: number;
|
|
151
151
|
}): Promise<RunRecord[]>;
|
|
152
|
+
/**
|
|
153
|
+
* S-297 —— 这个会话**最近一条腿**的窄指针(`/mcp` 面板的 `lastLegMcp` 与任何「上一条 run 是谁」的读面共用)。
|
|
154
|
+
*
|
|
155
|
+
* 排序口径与 {@link listSessions} 的 `rn=1` 窗函数**逐字同一句**(`created_at DESC, task_id DESC`)——
|
|
156
|
+
* 两处答的是同一个问题「哪一条是最新的」,口径分叉会让列表面的 `lastRunId` 与本读口指向不同的 run。
|
|
157
|
+
* 走的也是同一条索引路径(`idx_task_run_session_status` 的 `session_id` 前缀),所以**不新加索引**:
|
|
158
|
+
* 会话内行数有界,前缀定位之后的排序只在这一小撮行上。
|
|
159
|
+
*
|
|
160
|
+
* 两方言只差占位符(动态子句为零,不像 listRuns/listSessions 那样需要各自的构造器臂)。
|
|
161
|
+
*/
|
|
162
|
+
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
152
163
|
/** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
|
|
153
164
|
* (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
|
|
154
165
|
* session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
|
|
@@ -252,6 +252,11 @@ export class SqlRunStore {
|
|
|
252
252
|
const { rows } = await this.db.query(sql, params);
|
|
253
253
|
return rows.map((r) => this.rowToRun(r));
|
|
254
254
|
}
|
|
255
|
+
async latestRunForSession(sessionId) {
|
|
256
|
+
const { rows } = await this.db.query(this.q("SELECT task_id, owner, created_at FROM task_run WHERE session_id = ? ORDER BY created_at DESC, task_id DESC LIMIT 1", "SELECT task_id, owner, created_at FROM task_run WHERE session_id = $1 ORDER BY created_at DESC, task_id DESC LIMIT 1"), [sessionId]);
|
|
257
|
+
const r = rows[0];
|
|
258
|
+
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
259
|
+
}
|
|
255
260
|
async listSessions(opts) {
|
|
256
261
|
if (this.db.dialect === "tidb") {
|
|
257
262
|
const params = [];
|
|
@@ -244,6 +244,22 @@ export interface SessionSummary {
|
|
|
244
244
|
* titled OR the lister face has no session_meta (the legacy task_run aggregate / memory twins — honest degrade). */
|
|
245
245
|
title: string | null;
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* S-297 —— 「这个会话**最近一条腿**是哪一条 run」的窄读面(`latestRunForSession` 的回值)。
|
|
249
|
+
*
|
|
250
|
+
* 刻意**不是** {@link RunRecord}:那一形携 `result` 整只 blob,而本读面的三个消费点(run 身份 / 属主门 /
|
|
251
|
+
* 排序口径的自证)一个都用不到它 —— 与 `listSessions` 只提标量 `lastTaskRunId` 而不拉 `result` 是同一条
|
|
252
|
+
* 判据([ref]:列表/指针面只拉标量,不把 blob 拖进按会话的高频读)。
|
|
253
|
+
*
|
|
254
|
+
* `owner` = 该 run 的**提交 principal**(可为 null = 无属主的存量行),消费点据它判「这条腿的读面该不该
|
|
255
|
+
* 投给当前调用方」;它与会话的 canonical owner 会分叉,判据与先例逐字见 `run-store-sql.ts deleteBySession`
|
|
256
|
+
* 的头注(F-A)。`createdAt` = 排序键(ISO),三孪生同一口径 `created_at DESC, task_id DESC`。
|
|
257
|
+
*/
|
|
258
|
+
export interface LatestRunRef {
|
|
259
|
+
taskId: string;
|
|
260
|
+
owner: string | null;
|
|
261
|
+
createdAt: string;
|
|
262
|
+
}
|
|
247
263
|
export interface RunEvent {
|
|
248
264
|
seq: number;
|
|
249
265
|
type: string;
|
package/dist/runs.js
CHANGED
|
@@ -10,7 +10,7 @@ import { recordTurnActivity } from "./turn-activity.js";
|
|
|
10
10
|
import { CANCELLED_CODE, mintCancelledResult } from "./run-cancel-context.js";
|
|
11
11
|
import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
|
|
12
12
|
import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
|
|
13
|
-
import { defaultSubagentTailBus
|
|
13
|
+
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
14
14
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
|
|
15
15
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
16
16
|
import { errorText as errText } from "./observability/err-text.js";
|
|
@@ -275,19 +275,13 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
275
275
|
fleetPublisher?.onForwardEvent(e);
|
|
276
276
|
sink.onForwardEvent(e);
|
|
277
277
|
const bg = e.bgAgentId;
|
|
278
|
-
if (bg !== undefined)
|
|
279
|
-
defaultSubagentTailBus.
|
|
280
|
-
if (defaultSubagentTailBus.hasSubscribers(bg)) {
|
|
281
|
-
const f = projectTailFrame(e);
|
|
282
|
-
if (f)
|
|
283
|
-
defaultSubagentTailBus.publish(bg, f);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
278
|
+
if (bg !== undefined)
|
|
279
|
+
defaultSubagentTailBus.onForwardFrame(bg, tailContentMode, e);
|
|
286
280
|
},
|
|
287
281
|
onTaskNotification: (n, opts) => {
|
|
288
282
|
if (n.task_type === "workflow")
|
|
289
283
|
return;
|
|
290
|
-
defaultSubagentTailBus.
|
|
284
|
+
defaultSubagentTailBus.forgetHandle(n.task_id);
|
|
291
285
|
if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
|
|
292
286
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
293
287
|
}
|
package/dist/tool-approval.js
CHANGED
|
@@ -2,7 +2,7 @@ import { contentKindOf } from "./approval-content-kind.js";
|
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
import { uuidv7, MAX_RULE_TEXT_CHARS, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
4
4
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
5
|
-
import {
|
|
5
|
+
import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact.js";
|
|
6
6
|
import { hasBidiControls } from "./text-bidi.js";
|
|
7
7
|
import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerprint.js";
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
@@ -32,7 +32,7 @@ const DEFAULT_PARK_TOMBSTONE_TTL_MS = 60 * 60_000;
|
|
|
32
32
|
const DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX = 128;
|
|
33
33
|
const PARKING_RETRY_AFTER_MS = 250;
|
|
34
34
|
function ruleMaterialCarriesCredentialShape(command) {
|
|
35
|
-
return
|
|
35
|
+
return redactSecretsWithArms(command, { observe: false }).arms.has("full");
|
|
36
36
|
}
|
|
37
37
|
function durableRuleMaterial(material) {
|
|
38
38
|
if (material === undefined)
|
|
@@ -2,6 +2,7 @@ import { terminalProjection } from "@sema-agent/core";
|
|
|
2
2
|
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, 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
6
|
export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
6
7
|
let seq = startSeq;
|
|
7
8
|
let chain = Promise.resolve();
|
|
@@ -43,6 +44,8 @@ export function createLedgerSink(opts) {
|
|
|
43
44
|
};
|
|
44
45
|
const onEvent = async (ev) => {
|
|
45
46
|
beat(ev.type);
|
|
47
|
+
if (isModelSegmentBoundary(ev.type))
|
|
48
|
+
await flush();
|
|
46
49
|
switch (ev.type) {
|
|
47
50
|
case "text_delta": {
|
|
48
51
|
const td = ev;
|
|
@@ -55,15 +58,12 @@ export function createLedgerSink(opts) {
|
|
|
55
58
|
reasoning += ev.delta;
|
|
56
59
|
break;
|
|
57
60
|
case "tool_start":
|
|
58
|
-
await flush();
|
|
59
61
|
await append("tool_start", toolStartEventData(ev));
|
|
60
62
|
break;
|
|
61
63
|
case "tool_end":
|
|
62
|
-
await flush();
|
|
63
64
|
await append("tool_end", toolEndEventData(ev));
|
|
64
65
|
break;
|
|
65
66
|
case "status":
|
|
66
|
-
await flush();
|
|
67
67
|
{
|
|
68
68
|
const statusData = brainStatusEventData(ev);
|
|
69
69
|
beat("status", statusData);
|
|
@@ -71,49 +71,39 @@ export function createLedgerSink(opts) {
|
|
|
71
71
|
}
|
|
72
72
|
break;
|
|
73
73
|
case "context_usage":
|
|
74
|
-
await flush();
|
|
75
74
|
await append("context_usage", contextUsageEventData(ev));
|
|
76
75
|
break;
|
|
77
76
|
case "compacted":
|
|
78
|
-
await flush();
|
|
79
77
|
await append("compacted", compactedEventData(ev));
|
|
80
78
|
break;
|
|
81
79
|
case "diagnostics":
|
|
82
|
-
await flush();
|
|
83
80
|
await append("diagnostics", diagnosticsEventData(ev));
|
|
84
81
|
break;
|
|
85
82
|
case "steering_injected":
|
|
86
|
-
await flush();
|
|
87
83
|
await append("steering_injected", steeringInjectedEventData(ev));
|
|
88
84
|
break;
|
|
89
85
|
case "compaction_outcome":
|
|
90
|
-
await flush();
|
|
91
86
|
await append("compaction_outcome", compactionOutcomeEventData(ev));
|
|
92
87
|
break;
|
|
93
88
|
case "workspace_changed":
|
|
94
|
-
await flush();
|
|
95
89
|
await append("workspace_changed", workspaceChangedEventData(ev));
|
|
96
90
|
break;
|
|
97
91
|
case "wiring_manifest":
|
|
98
|
-
await flush();
|
|
99
92
|
await append("wiring_manifest", wiringManifestEventData(ev));
|
|
100
93
|
break;
|
|
101
94
|
case "tool_roster_delta": {
|
|
102
|
-
await flush();
|
|
103
95
|
const rosterDelta = toolRosterDeltaEventData(ev);
|
|
104
96
|
if (rosterDelta !== undefined)
|
|
105
97
|
await append("tool_roster_delta", rosterDelta);
|
|
106
98
|
break;
|
|
107
99
|
}
|
|
108
100
|
case "human_input":
|
|
109
|
-
await flush();
|
|
110
101
|
await append("human_input", humanInputEventData(ev));
|
|
111
102
|
break;
|
|
112
103
|
case "task_progress":
|
|
113
104
|
await append("task_progress", taskProgressEventData(ev));
|
|
114
105
|
break;
|
|
115
106
|
case "task_notification": {
|
|
116
|
-
await flush();
|
|
117
107
|
const tn = ev.notification;
|
|
118
108
|
const tnKey = tn?.task_id ? taskNotificationStreamKey({ task_type: tn.task_type, task_id: tn.task_id, status: String(tn.status), seq: tn.seq }) : undefined;
|
|
119
109
|
const tnPrior = tnKey !== undefined ? keys.get(tnKey) : undefined;
|
|
@@ -126,7 +116,6 @@ export function createLedgerSink(opts) {
|
|
|
126
116
|
break;
|
|
127
117
|
}
|
|
128
118
|
case "turn_end": {
|
|
129
|
-
await flush();
|
|
130
119
|
const usage = ev.usage;
|
|
131
120
|
await append("turn_end", turnEndEventData(ev));
|
|
132
121
|
textEventId = undefined;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本帧是不是模型文本的段界。**四条消费腿共用这一只**:账本投影 switch(`trace/ledger-sink.ts`)、
|
|
3
|
+
* 顶层 live 流与转发子代腿(`http/routes/tasks.ts`)、子代 tail 面(`fleet/subagent-tail-bus.ts`)。
|
|
4
|
+
* 未知/未来词一律 `false` —— 不是兜底,是上面三道钉保证「未知词不存在」之后剩下的诚实答案
|
|
5
|
+
* (第三方 Runner 塞进来的野帧不该切走宿主的权威段)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function isModelSegmentBoundary(type: string): boolean;
|
|
8
|
+
/** live 面的段末帧(`type` 即事件名;其余键由 `reasoningEndEventData` 白名单铸出)。 */
|
|
9
|
+
export type ReasoningEndFrame = Record<string, unknown> & {
|
|
10
|
+
type: "reasoning_end";
|
|
11
|
+
};
|
|
12
|
+
/** 登记册**读到的全部字段**,逐个写出来(不是 `Record<string, unknown>`):`TaskEvent` 与转发腿的原始帧
|
|
13
|
+
* 都结构性满足它,于是调用点一个宽断言都不必写;将来有人想读第七个键,得先在这里加一行。 */
|
|
14
|
+
export interface ReasoningStreamEvent {
|
|
15
|
+
type?: string;
|
|
16
|
+
delta?: unknown;
|
|
17
|
+
eventId?: unknown;
|
|
18
|
+
parentToolCallId?: unknown;
|
|
19
|
+
sourceTaskId?: unknown;
|
|
20
|
+
bgAgentId?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** 一条**流**的身份(不含 `eventId`:core 给每个内容事件铸各自的 eventId,拿它当流身份会把每块 delta
|
|
23
|
+
* 切成一段)。三键都缺席 = 宿主自己的流。 */
|
|
24
|
+
export interface ReasoningStreamIdent {
|
|
25
|
+
parentToolCallId?: string;
|
|
26
|
+
sourceTaskId?: string;
|
|
27
|
+
bgAgentId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 一条 live 腿上**按流身份分账**的 `reasoning` 段登记册。
|
|
31
|
+
*
|
|
32
|
+
* 🔴 **为什么是登记册而不是一只累积器**(codex 对抗复审 R1 两根 [high],亲跑复现后按类修):一条腿上
|
|
33
|
+
* 并发子代的 `reasoning_delta` 是**交错**到达的。一只累积器只有两种活法,两条都错:
|
|
34
|
+
* · 「换身份就收段」⇒ A 说一半、B 插一句、A 说另一半 ⇒ A 被切成两枚权威段,**跨块的凭据正好落在切口上**
|
|
35
|
+
* ——本帧存在的唯一理由当场失效(实测:两枚 `reasoning_end` 拼起来仍是明文密钥);
|
|
36
|
+
* · 「不换就不收」⇒ 两个子代的思考拼成一段,再盖上其中一个的身份键(串味 + 冤枉归属)。
|
|
37
|
+
* 分账之后规则反而**更少**:一条流的段,只被**它自己**的段界或**它自己**的终态收走;别人说什么都与它无关。
|
|
38
|
+
*
|
|
39
|
+
* 状态的界:每条流的条目在它的首块 delta 上建、在收段时删;腿终局 {@link ReasoningSegments.cut} 不带
|
|
40
|
+
* 匹配器 = 全收全删。子代终态那一次带匹配器的收段(转发腿的 `onTaskNotification`)是让**活着的子代数**
|
|
41
|
+
* 而不是「这条 run 一共派过多少子代」成为上界的那一手。
|
|
42
|
+
*/
|
|
43
|
+
export interface ReasoningSegments {
|
|
44
|
+
/** 喂一帧 → 本帧收走的段末帧(0 或 1 枚,且**只可能是本帧自己那条流**的段)。返回数组只为让调用点
|
|
45
|
+
* 与 {@link cut} 同形(`for (const f of …) send(f)`),不是「一帧可能收多段」。 */
|
|
46
|
+
onEvent(ev: ReasoningStreamEvent): ReasoningEndFrame[];
|
|
47
|
+
/** 收段:`match` 缺省 = 本腿**全部**流(腿终局 / 句柄终态);给了就只收匹配的那几条(某个子代终态)。
|
|
48
|
+
* 与账本腿 `LedgerSink.onDone → flush()` 同位同义 —— 段界表里没有终局词,终局走这扇门。 */
|
|
49
|
+
cut(match?: (ident: ReasoningStreamIdent) => boolean): ReasoningEndFrame[];
|
|
50
|
+
}
|
|
51
|
+
/** 「这条流属于这些子代 id 之一」—— 子代终态收段的匹配器(`task_id` 与 `sessionId` 是同一个子代的两个
|
|
52
|
+
* id 域,取并集;`undefined` 成员自动忽略)。腿上共用这一只,免得各写一遍 id 域的并集。 */
|
|
53
|
+
export declare function reasoningStreamOfTask(...ids: ReadonlyArray<string | undefined>): (ident: ReasoningStreamIdent) => boolean;
|
|
54
|
+
export declare function createReasoningSegments(): ReasoningSegments;
|
|
55
|
+
//# sourceMappingURL=model-segment.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { reasoningEndEventData } from "./project.js";
|
|
2
|
+
const BOUNDARY = new Set([
|
|
3
|
+
"tool_start", "tool_end", "status", "context_usage", "compacted", "diagnostics",
|
|
4
|
+
"steering_injected", "compaction_outcome", "workspace_changed", "wiring_manifest",
|
|
5
|
+
"tool_roster_delta", "human_input", "task_notification", "turn_end",
|
|
6
|
+
]);
|
|
7
|
+
export function isModelSegmentBoundary(type) {
|
|
8
|
+
return BOUNDARY.has(type);
|
|
9
|
+
}
|
|
10
|
+
export function reasoningStreamOfTask(...ids) {
|
|
11
|
+
const set = new Set(ids.filter((v) => typeof v === "string" && v !== ""));
|
|
12
|
+
return (ident) => (ident.sourceTaskId !== undefined && set.has(ident.sourceTaskId)) || (ident.bgAgentId !== undefined && set.has(ident.bgAgentId));
|
|
13
|
+
}
|
|
14
|
+
function streamKeyOf(ident) {
|
|
15
|
+
return `${ident.parentToolCallId ?? ""}\u0000${ident.sourceTaskId ?? ""}\u0000${ident.bgAgentId ?? ""}`;
|
|
16
|
+
}
|
|
17
|
+
function identOf(ev) {
|
|
18
|
+
const s = (v) => (typeof v === "string" ? v : undefined);
|
|
19
|
+
return { parentToolCallId: s(ev.parentToolCallId), sourceTaskId: s(ev.sourceTaskId), bgAgentId: s(ev.bgAgentId) };
|
|
20
|
+
}
|
|
21
|
+
export function createReasoningSegments() {
|
|
22
|
+
const open = new Map();
|
|
23
|
+
const cutKey = (key) => {
|
|
24
|
+
const seg = open.get(key);
|
|
25
|
+
if (seg === undefined)
|
|
26
|
+
return [];
|
|
27
|
+
open.delete(key);
|
|
28
|
+
const data = reasoningEndEventData({ ...seg.ident, eventId: seg.eventId, content: seg.buf });
|
|
29
|
+
return data === null ? [] : [{ type: "reasoning_end", ...data }];
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
onEvent: (ev) => {
|
|
33
|
+
if (ev.type === "reasoning_delta") {
|
|
34
|
+
if (typeof ev.delta !== "string" || ev.delta === "")
|
|
35
|
+
return [];
|
|
36
|
+
const ident = identOf(ev);
|
|
37
|
+
const key = streamKeyOf(ident);
|
|
38
|
+
const seg = open.get(key);
|
|
39
|
+
if (seg === undefined)
|
|
40
|
+
open.set(key, { ident, eventId: ev.eventId, buf: ev.delta });
|
|
41
|
+
else
|
|
42
|
+
seg.buf += ev.delta;
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return isModelSegmentBoundary(ev.type ?? "") ? cutKey(streamKeyOf(identOf(ev))) : [];
|
|
46
|
+
},
|
|
47
|
+
cut: (match) => {
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const [key, seg] of [...open]) {
|
|
50
|
+
if (match !== undefined && !match(seg.ident))
|
|
51
|
+
continue;
|
|
52
|
+
out.push(...cutKey(key));
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=model-segment.js.map
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -194,6 +194,23 @@ export declare function screenedStoredEventData(type: string, data: unknown): un
|
|
|
194
194
|
* 坏形(content 非串/空串)⇒ null(丢帧,不铸「权威空段」:core 契约明言空段不发本帧,消费端收到空段会清掉已攒 delta)。
|
|
195
195
|
* 身份四键零字节变换(live 现状带全四键,消费端可能已依赖)。 */
|
|
196
196
|
export declare function textEndEventData(ev: Record<string, unknown>): Record<string, unknown> | null;
|
|
197
|
+
/**
|
|
198
|
+
* `reasoning_end` 段末帧的**唯一**铸点(三条 live 腿共用:top stream / 转发子代腿 / 子代 tail 面)——
|
|
199
|
+
* `text_end` 的推理面孪生({@link textEndEventData}),形逐字同(共用 {@link segmentEndFields})。
|
|
200
|
+
*
|
|
201
|
+
* 🔴 **本帧由 server 自铸,core 不发**(`TaskEvent` 上只有 `reasoning_delta`)。`content` = 该段
|
|
202
|
+
* `reasoning_delta` 的累积原文,经与 durable 面**同一只** `redactModelText` —— 这正是本帧存在的理由:
|
|
203
|
+
* 逐 chunk 的 `reasoning_delta` 对**跨 chunk** 的凭据形无能为力(半截 `sk-` 在任何一块里都不成形),
|
|
204
|
+
* 而账本腿攒到 flush 边界的那一整段早就脱得干净;live 面此前没有这份权威副本,壳无可替换。
|
|
205
|
+
* 段界与账本腿**同一只谓词**(`trace/model-segment.ts` 的 `isModelSegmentBoundary`),所以同一次运行里
|
|
206
|
+
* 「账本 `reasoning` 行」与本帧的正文逐字节相等。
|
|
207
|
+
*
|
|
208
|
+
* **空段不发**,判据比 `text_end` 宽一格(那边是 core 契约的「空段不发」,只需判空串):本帧的正文由
|
|
209
|
+
* 本仓攒出,一段**全空白**的思考(模型吐了几个换行就去调工具)没有可替换的权威正文,而消费律是
|
|
210
|
+
* 「整段替换已攒的 delta」—— 发一枚空白权威段 = 让消费端把已渲染的思考清成空白。坏形(`content` 非串)
|
|
211
|
+
* 同样丢帧,理由与 `text_end` 逐字同源。
|
|
212
|
+
*/
|
|
213
|
+
export declare function reasoningEndEventData(ev: Record<string, unknown>): Record<string, unknown> | null;
|
|
197
214
|
/** The durable `tool_end` payload (shared by both append sites). E1: the model-facing `output` is UNTRUSTED RAW
|
|
198
215
|
* from core (file/shell/API bodies) — core only SIZE-bounds it (`truncated`), REDACTION is ours (same contract as
|
|
199
216
|
* `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
|
@@ -176,14 +176,22 @@ function projectSettlement(st) {
|
|
|
176
176
|
export function textEndEventData(ev) {
|
|
177
177
|
if (typeof ev.content !== "string" || ev.content.length === 0)
|
|
178
178
|
return null;
|
|
179
|
+
return segmentEndFields(ev.content, ev);
|
|
180
|
+
}
|
|
181
|
+
function segmentEndFields(content, ev) {
|
|
179
182
|
return {
|
|
180
|
-
content: redactModelText(
|
|
183
|
+
content: redactModelText(content),
|
|
181
184
|
...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
|
|
182
185
|
...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
|
|
183
186
|
...(ev.sourceTaskId !== undefined ? { sourceTaskId: ev.sourceTaskId } : {}),
|
|
184
187
|
...(ev.bgAgentId !== undefined ? { bgAgentId: ev.bgAgentId } : {}),
|
|
185
188
|
};
|
|
186
189
|
}
|
|
190
|
+
export function reasoningEndEventData(ev) {
|
|
191
|
+
if (typeof ev.content !== "string" || ev.content.trim().length === 0)
|
|
192
|
+
return null;
|
|
193
|
+
return segmentEndFields(ev.content, ev);
|
|
194
|
+
}
|
|
187
195
|
export function toolEndEventData(ev) {
|
|
188
196
|
const gate = ev.gate === undefined ? undefined : screenedGate(ev.gate);
|
|
189
197
|
return {
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -69,7 +69,9 @@ export declare function resetPpkScanBytesForTest(): void;
|
|
|
69
69
|
* core [ref] 落地后这一支**趋零是证据、不归零不是证明**。别把它当截断事实的度量用。 */
|
|
70
70
|
export type RedactionArm = "full" | "fragment";
|
|
71
71
|
export interface RedactionHitMeta {
|
|
72
|
-
/**
|
|
72
|
+
/** 命中臂的**判别力档位**(闭集两词,S-303):`"fragment"` = {@link FRAGMENT_SECRET_PATTERNS} 那一族
|
|
73
|
+
* 「短前缀 + 长度/熵阈值」的弱证据臂;`"full"` = 其余全部(结构锚 / 词法关键字 / 定长裸形)。
|
|
74
|
+
* 判据 = 表归属,不是 token 的 `:partial` 后缀 —— 两条轴的分家逐字见 {@link armOfToken}。 */
|
|
73
75
|
arm: RedactionArm;
|
|
74
76
|
}
|
|
75
77
|
export declare function setRedactionObserver(fn: ((patternLabel: string, count: number, meta: RedactionHitMeta) => void) | undefined): void;
|
|
@@ -77,6 +79,26 @@ export declare function redactSecrets(text: string, opts?: {
|
|
|
77
79
|
observe?: boolean;
|
|
78
80
|
json?: boolean;
|
|
79
81
|
}): string;
|
|
82
|
+
/**
|
|
83
|
+
* 脱敏 + **这一遍命中了哪些档位的臂**({@link RedactionArm} 闭集两词)—— 与 {@link redactSecrets} 同一只
|
|
84
|
+
* 脱敏器、同一张表、同一遍扫描的另一个出口。
|
|
85
|
+
*
|
|
86
|
+
* 🔴 **为什么要这个出口**(S-303):下游有一类消费方问的不是「脱完长什么样」而是「这段文本里有没有
|
|
87
|
+
* **强证据**的凭据形」——`tool-approval.ts` 的规则素材闸是头一个。旧形只能拿「字节变了没有」当谓词,
|
|
88
|
+
* 于是弱证据臂(短前缀 + 阈值启发式)的一次多脱就等于一次「素材整只扣下」,用户拿不到常驻放行。
|
|
89
|
+
* 出口开在脱敏器**自己**身上是硬约束:全仓只该有一个「什么算凭据」的属主,消费方**不许**按 token 字面
|
|
90
|
+
* (`includes(":partial»")`)或另跑一遍正则去反推 —— 那就是第二只脱敏器。
|
|
91
|
+
*
|
|
92
|
+
* `arms` 是**这一遍真命中的档位集合**:空集 = 一条臂都没命中(文本可能仍被归一化剥了 `\p{Cf}`,
|
|
93
|
+
* 所以「`text !== 入参`」与「`arms` 非空」不是一回事,后者才是凭据形的证据)。
|
|
94
|
+
*/
|
|
95
|
+
export declare function redactSecretsWithArms(text: string, opts?: {
|
|
96
|
+
observe?: boolean;
|
|
97
|
+
json?: boolean;
|
|
98
|
+
}): {
|
|
99
|
+
text: string;
|
|
100
|
+
arms: ReadonlySet<RedactionArm>;
|
|
101
|
+
};
|
|
80
102
|
/**
|
|
81
103
|
* 🔴 环 + 深度预算(2026-07-25 补)。原实现是纯递归、**既不记已访问节点也不限深度**,而它的调用位置是
|
|
82
104
|
* **最不可信的边界**:`toolStart`/`toolEnd` 把工具的 `args`/`output`/`structured` 直接喂进来。后果两条:
|
package/dist/trace/redact.js
CHANGED
|
@@ -358,7 +358,7 @@ const SHAPE_SECRET_PATTERNS = [
|
|
|
358
358
|
[new RegExp(String.raw `xapp-\d-${atLeast("[A-Za-z0-9-]", 20)}`, "g"), "«redacted:slack-token»"],
|
|
359
359
|
[valueArm("sk-", "[A-Za-z0-9_-]", 16, "self-evident"), "«redacted:key»"],
|
|
360
360
|
];
|
|
361
|
-
const
|
|
361
|
+
const FULL_ARM_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
|
|
362
362
|
const PEM_HEADER_IDENT_MAX = 31;
|
|
363
363
|
const PEM_BODY_RUN_MIN = 8;
|
|
364
364
|
const isSepCode = (c) => c === CH_SPACE || c === CH_TAB || c === CH_CR || c === CH_LF;
|
|
@@ -743,7 +743,7 @@ const FRAGMENT_SECRET_PATTERNS = [
|
|
|
743
743
|
[valueArm("(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-|(?:sk|rk)_(?:live|test|org)_|pypi-)", "[A-Za-z0-9_-]", 8, "needs-left-boundary"), "«redacted:key:partial»"],
|
|
744
744
|
[valueArm("xapp-\\d-", "[A-Za-z0-9-]", 4, "needs-left-boundary"), "«redacted:slack-token:partial»"],
|
|
745
745
|
];
|
|
746
|
-
const REDACTION_TOKENS = new Set([...
|
|
746
|
+
const REDACTION_TOKENS = new Set([...FULL_ARM_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
|
|
747
747
|
.flatMap((r) => r.match(/«[^»]*»/g) ?? []));
|
|
748
748
|
const REDACTION_TOKEN_MAX = Math.max(...[...REDACTION_TOKENS].map((t) => t.length));
|
|
749
749
|
{
|
|
@@ -761,7 +761,20 @@ function redactionTokenEndAt(s, p, limit) {
|
|
|
761
761
|
return 0;
|
|
762
762
|
}
|
|
763
763
|
const labelOfToken = (token) => token.replace(/[«»]/g, "").replace(/^redacted:/, "");
|
|
764
|
-
const
|
|
764
|
+
const FRAGMENT_ARM_TOKENS = new Set(FRAGMENT_SECRET_PATTERNS.map(([, repl]) => repl));
|
|
765
|
+
{
|
|
766
|
+
const partialSuffix = ":partial»";
|
|
767
|
+
const hasPartialToken = (repl) => (repl.match(/«[^»]*»/g) ?? [repl]).some((t) => t.endsWith(partialSuffix));
|
|
768
|
+
for (const [, repl] of FRAGMENT_SECRET_PATTERNS) {
|
|
769
|
+
if (!hasPartialToken(repl))
|
|
770
|
+
throw new Error(`redact: fragment-arm replacement ${JSON.stringify(repl)} must end with ${partialSuffix}`);
|
|
771
|
+
}
|
|
772
|
+
for (const [, repl] of [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS]) {
|
|
773
|
+
if (hasPartialToken(repl))
|
|
774
|
+
throw new Error(`redact: strong-arm replacement ${JSON.stringify(repl)} must not end with ${partialSuffix}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const armOfToken = (repl) => (FRAGMENT_ARM_TOKENS.has(repl) ? "fragment" : "full");
|
|
765
778
|
const NO_HITS = [];
|
|
766
779
|
const singleHit = (label, arm, r) => r.count > 0 ? { out: r.out, hits: [{ label, count: r.count, arm }] } : { out: r.out, hits: NO_HITS };
|
|
767
780
|
const regexRule = ([re, repl]) => {
|
|
@@ -789,9 +802,9 @@ const regexRule = ([re, repl]) => {
|
|
|
789
802
|
return { run: (text, wantCount) => singleHit(label, arm, applyOnce(text, wantCount)) };
|
|
790
803
|
};
|
|
791
804
|
const URL_USERINFO_PATTERN = (() => {
|
|
792
|
-
const row =
|
|
805
|
+
const row = FULL_ARM_PATTERNS.find(([, repl]) => repl.endsWith("»@"));
|
|
793
806
|
if (row === undefined)
|
|
794
|
-
throw new Error("redact: URL-userinfo rule missing from
|
|
807
|
+
throw new Error("redact: URL-userinfo rule missing from FULL_ARM_PATTERNS");
|
|
795
808
|
return row;
|
|
796
809
|
})();
|
|
797
810
|
const isRegexSpace = (c) => c === 32 || (c >= 9 && c <= 13) || c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;
|
|
@@ -1021,10 +1034,24 @@ const armPoolRule = (() => {
|
|
|
1021
1034
|
const out = applySpans(text, kept).out;
|
|
1022
1035
|
if (!wantCount || kept.length === 0)
|
|
1023
1036
|
return { out, hits: NO_HITS };
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1037
|
+
const strong = spans.filter((sp) => armOfToken(sp.repl) === "full").sort((a, b) => a.start - b.start);
|
|
1038
|
+
const byArmedToken = new Map();
|
|
1039
|
+
let si = 0;
|
|
1040
|
+
let strongMaxEnd = -1;
|
|
1041
|
+
for (const sp of kept) {
|
|
1042
|
+
while (si < strong.length && strong[si].start < sp.end) {
|
|
1043
|
+
strongMaxEnd = Math.max(strongMaxEnd, strong[si].end);
|
|
1044
|
+
si += 1;
|
|
1045
|
+
}
|
|
1046
|
+
const arm = strongMaxEnd > sp.start ? "full" : armOfToken(sp.repl);
|
|
1047
|
+
const key = `${sp.repl}\u0000${arm}`;
|
|
1048
|
+
const cur = byArmedToken.get(key);
|
|
1049
|
+
if (cur === undefined)
|
|
1050
|
+
byArmedToken.set(key, { token: sp.repl, arm, count: 1 });
|
|
1051
|
+
else
|
|
1052
|
+
cur.count += 1;
|
|
1053
|
+
}
|
|
1054
|
+
return { out, hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })) };
|
|
1028
1055
|
},
|
|
1029
1056
|
};
|
|
1030
1057
|
})();
|
|
@@ -1063,13 +1090,20 @@ function reportHits(hits) {
|
|
|
1063
1090
|
}
|
|
1064
1091
|
}
|
|
1065
1092
|
}
|
|
1066
|
-
|
|
1067
|
-
const wantCount = onRedaction !== undefined &&
|
|
1068
|
-
const
|
|
1069
|
-
const r = applyRules(normalizeForRedaction(text), wantCount, json, opts?.observe ?? true);
|
|
1093
|
+
function runRedaction(text, observe, json, wantHits) {
|
|
1094
|
+
const wantCount = wantHits || (onRedaction !== undefined && observe);
|
|
1095
|
+
const r = applyRules(normalizeForRedaction(text), wantCount, json, observe);
|
|
1070
1096
|
forgetEscapedSeparatorTails();
|
|
1071
|
-
|
|
1072
|
-
|
|
1097
|
+
if (observe)
|
|
1098
|
+
reportHits(r.hits);
|
|
1099
|
+
return { text: r.out, hits: r.hits };
|
|
1100
|
+
}
|
|
1101
|
+
export function redactSecrets(text, opts) {
|
|
1102
|
+
return runRedaction(text, opts?.observe ?? true, opts?.json === true, false).text;
|
|
1103
|
+
}
|
|
1104
|
+
export function redactSecretsWithArms(text, opts) {
|
|
1105
|
+
const r = runRedaction(text, opts?.observe ?? true, opts?.json === true, true);
|
|
1106
|
+
return { text: r.text, arms: new Set(r.hits.map((h) => h.arm)) };
|
|
1073
1107
|
}
|
|
1074
1108
|
const REDACT_MAX_DEPTH = 64;
|
|
1075
1109
|
export function redactDeep(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.77.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",
|