@sema-agent/server 7.124.1 → 7.125.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +138 -2
- package/USAGE.md +46 -8
- package/dist/audit.d.ts +27 -5
- package/dist/audit.js +42 -8
- package/dist/boot/execution-env.d.ts +33 -1
- package/dist/boot/execution-env.js +31 -2
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/session-faces.d.ts +14 -1
- package/dist/boot/session-faces.js +6 -5
- package/dist/boot/stage-02-web-admin-pull.js +5 -1
- package/dist/boot/stage-05-execution-env.js +6 -2
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/web-admin.js +130 -140
- package/dist/capabilities/center-wiring.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capability-facts.d.ts +28 -3
- package/dist/capability-facts.js +20 -0
- package/dist/config-catalog.js +4 -1
- package/dist/config-d-migration.js +2 -1
- package/dist/config-lkg.d.ts +4 -1
- package/dist/config-lkg.js +16 -12
- package/dist/config-provider.d.ts +4 -1
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +10 -3
- package/dist/config.js +44 -14
- package/dist/env-facts.d.ts +2 -2
- package/dist/env-facts.js +3 -2
- package/dist/fleet/fleet-bus.d.ts +24 -12
- package/dist/fleet/fleet-bus.js +4 -1
- package/dist/fleet/workspace-snapshot-residual.d.ts +10 -0
- package/dist/fleet/workspace-snapshot-residual.js +35 -0
- package/dist/hooks/hook-runner.d.ts +15 -27
- package/dist/hooks/hook-runner.js +19 -59
- package/dist/http/active-run-conflict.d.ts +3 -0
- package/dist/http/active-run-conflict.js +3 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +2 -4
- package/dist/http/device-placement.js +1 -0
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.d.ts +20 -6
- package/dist/http/durable-run-leg.js +4 -4
- package/dist/http/idempotency.d.ts +83 -38
- package/dist/http/idempotency.js +38 -13
- package/dist/http/request-guards.d.ts +18 -0
- package/dist/http/request-guards.js +12 -3
- package/dist/http/resume-legs.js +7 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/a2a-serve.js +27 -11
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/fleet.js +2 -0
- package/dist/http/routes/images.js +2 -2
- package/dist/http/routes/leader.d.ts +4 -0
- package/dist/http/routes/leader.js +13 -3
- package/dist/http/routes/observability.js +2 -0
- package/dist/http/routes/runs.d.ts +6 -2
- package/dist/http/routes/runs.js +71 -101
- package/dist/http/routes/session-sync.d.ts +3 -0
- package/dist/http/routes/session-sync.js +30 -1
- package/dist/http/routes/sessions-list.d.ts +11 -0
- package/dist/http/routes/sessions-list.js +6 -6
- package/dist/http/routes/sessions.d.ts +6 -1
- package/dist/http/routes/sessions.js +41 -10
- package/dist/http/routes/tasks.js +63 -21
- package/dist/http/run-read-face.d.ts +26 -0
- package/dist/http/run-read-face.js +67 -0
- package/dist/http/server.d.ts +4 -4
- package/dist/http/server.js +1 -2
- package/dist/http/submit-replay.d.ts +40 -0
- package/dist/http/submit-replay.js +22 -0
- package/dist/leader/endpoint.d.ts +10 -5
- package/dist/leader/endpoint.js +27 -5
- package/dist/observability/cost-quota.d.ts +51 -7
- package/dist/observability/cost-quota.js +35 -17
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/run-terminal.d.ts +5 -9
- package/dist/observability/run-terminal.js +2 -7
- package/dist/plugins/approval-ask-store-sql.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +7 -2
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/device-store-sql.js +1 -1
- package/dist/plugins/ensure-index.js +22 -0
- package/dist/plugins/file-run-store.d.ts +24 -14
- package/dist/plugins/file-run-store.js +52 -12
- package/dist/plugins/leader-run-store-sql.d.ts +1 -11
- package/dist/plugins/leader-run-store-sql.js +1 -5
- package/dist/plugins/local-session-store.d.ts +35 -6
- package/dist/plugins/local-session-store.js +87 -18
- package/dist/plugins/memory-run-store.d.ts +13 -12
- package/dist/plugins/memory-run-store.js +25 -0
- package/dist/plugins/one-time-migrations.js +1 -1
- package/dist/plugins/pg-cost-quota.d.ts +1 -1
- package/dist/plugins/pg-cost-quota.js +2 -5
- package/dist/plugins/pg-pool.js +3 -0
- package/dist/plugins/pg-session-storage.d.ts +11 -1
- package/dist/plugins/pg-session-storage.js +44 -8
- package/dist/plugins/remote-env-k8s.d.ts +127 -12
- package/dist/plugins/remote-env-k8s.js +270 -27
- package/dist/plugins/run-store-sql.d.ts +26 -12
- package/dist/plugins/run-store-sql.js +50 -25
- package/dist/plugins/schema-consistency.d.ts +1 -13
- package/dist/plugins/schema-consistency.js +1 -41
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/schema-live-columns.d.ts +20 -0
- package/dist/plugins/schema-live-columns.js +42 -0
- package/dist/plugins/store-contracts.d.ts +40 -2
- package/dist/plugins/tidb-cost-quota.d.ts +1 -1
- package/dist/plugins/tidb-cost-quota.js +2 -5
- package/dist/plugins/tidb-pool.js +14 -3
- package/dist/plugins/tidb-session-store.d.ts +22 -2
- package/dist/plugins/tidb-session-store.js +33 -6
- package/dist/plugins/write-behind-counter.d.ts +12 -5
- package/dist/plugins/write-behind-counter.js +14 -7
- package/dist/prompts-domain-validate.js +3 -2
- package/dist/rules-consent.d.ts +2 -2
- package/dist/rules-consent.js +2 -2
- package/dist/run-local.js +31 -23
- package/dist/runs.d.ts +9 -0
- package/dist/runs.js +8 -0
- package/dist/runtime-governance.d.ts +3 -3
- package/dist/runtime-governance.js +2 -4
- package/dist/security.d.ts +19 -2
- package/dist/session-row.d.ts +53 -0
- package/dist/session-row.js +47 -0
- package/dist/session-sync.d.ts +6 -0
- package/dist/session-sync.js +5 -1
- package/dist/session-titler.d.ts +9 -5
- package/dist/session-titler.js +3 -1
- package/dist/spec-fields.js +2 -2
- package/dist/task-cwd.d.ts +0 -20
- package/dist/task-cwd.js +2 -10
- package/dist/tool-approval.d.ts +5 -7
- package/dist/trace/redact.d.ts +23 -50
- package/dist/trace/redact.js +42 -9
- package/dist/trace/self-minted-wire.d.ts +25 -0
- package/dist/trace/self-minted-wire.js +9 -0
- package/dist/web-admin-client/apply-effective.d.ts +10 -12
- package/dist/web-admin-client/apply-effective.js +2 -3
- package/dist/web-admin-client/candidate-gate.d.ts +69 -0
- package/dist/web-admin-client/candidate-gate.js +27 -0
- package/dist/web-admin-client/http-client.d.ts +2 -1
- package/dist/web-admin-client/http-client.js +2 -1
- package/dist/web-admin-client/pull-state.d.ts +6 -4
- package/dist/web-admin-client/read-warnings.d.ts +29 -5
- package/dist/web-admin-client/read-warnings.js +24 -4
- package/dist/web-admin-client/stage-limits.js +4 -1
- package/dist/workspace-space.d.ts +41 -0
- package/dist/workspace-space.js +5 -0
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { alignedWindow, retryAfterSecUntil } from "../observability/cost-quota.js";
|
|
1
2
|
const SEP = "\u0000";
|
|
2
3
|
export const DEFAULT_COUNTER_QUERY_TIMEOUT_MS = 30_000;
|
|
3
4
|
export class SqlWriteBehindCounter {
|
|
@@ -48,24 +49,30 @@ export class SqlWriteBehindCounter {
|
|
|
48
49
|
catch {
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
window() {
|
|
53
|
+
return alignedWindow(this.now(), this.windowMs);
|
|
54
|
+
}
|
|
51
55
|
bucket() {
|
|
52
|
-
return
|
|
56
|
+
return this.window().bucket;
|
|
53
57
|
}
|
|
54
|
-
rk(key) {
|
|
55
|
-
return `${key}${SEP}${
|
|
58
|
+
rk(key, bucket) {
|
|
59
|
+
return `${key}${SEP}${bucket}`;
|
|
56
60
|
}
|
|
57
|
-
|
|
58
|
-
const rk = this.rk(key);
|
|
61
|
+
usedIn(key, bucket) {
|
|
62
|
+
const rk = this.rk(key, bucket);
|
|
59
63
|
return (this.fleetTotal.get(rk) ?? 0) + (this.pendingDelta.get(rk) ?? 0);
|
|
60
64
|
}
|
|
65
|
+
used(key) {
|
|
66
|
+
return this.usedIn(key, this.bucket());
|
|
67
|
+
}
|
|
61
68
|
addLocal(key, n) {
|
|
62
69
|
if (n <= 0)
|
|
63
70
|
return;
|
|
64
|
-
const rk = this.rk(key);
|
|
71
|
+
const rk = this.rk(key, this.bucket());
|
|
65
72
|
this.pendingDelta.set(rk, (this.pendingDelta.get(rk) ?? 0) + n);
|
|
66
73
|
}
|
|
67
74
|
retryAfterSec() {
|
|
68
|
-
return
|
|
75
|
+
return retryAfterSecUntil(this.window());
|
|
69
76
|
}
|
|
70
77
|
async flush() {
|
|
71
78
|
if (this.flushing) {
|
|
@@ -3,9 +3,10 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { verifyPromptArtifact } from "@sema-agent/core";
|
|
6
|
+
import { PROMPT_SLOTS } from "@sema-agent/settings-schema";
|
|
6
7
|
import { SHA256_DIGEST_RE } from "./digest-form.js";
|
|
7
8
|
const ID_RE = /^[a-z0-9][a-z0-9/._-]*$/;
|
|
8
|
-
const SLOTS =
|
|
9
|
+
const SLOTS = PROMPT_SLOTS;
|
|
9
10
|
function declarationIssue(d, where) {
|
|
10
11
|
if (d === null || typeof d !== "object")
|
|
11
12
|
return `${where}: declaration is not an object`;
|
|
@@ -14,7 +15,7 @@ function declarationIssue(d, where) {
|
|
|
14
15
|
return `${where}: bad id`;
|
|
15
16
|
if (o.id.startsWith("core/"))
|
|
16
17
|
return `${where}: id in the reserved core/ namespace`;
|
|
17
|
-
if (typeof o.slot !== "string" || !SLOTS.
|
|
18
|
+
if (typeof o.slot !== "string" || !SLOTS.includes(o.slot))
|
|
18
19
|
return `${where}: bad slot`;
|
|
19
20
|
if (typeof o.text !== "string" || o.text.length === 0)
|
|
20
21
|
return `${where}: text must be a non-empty string`;
|
package/dist/rules-consent.d.ts
CHANGED
|
@@ -479,7 +479,7 @@ export declare function serializeRuleScope(scope: RuleScope): string;
|
|
|
479
479
|
* 🔴 判别式串的**上限,由铸侧的尺派生**([ref],2026-08-19 合并重扫 confirmed —— 修的是「两个数字
|
|
480
480
|
* 各挑各的」这件事本身,不是把某个数字调大一点)。
|
|
481
481
|
*
|
|
482
|
-
* 病:铸侧 `cardRuleScopeRoot` 对 root 长度零把关(入口尺 = `
|
|
482
|
+
* 病:铸侧 `cardRuleScopeRoot` 对 root 长度零把关(入口尺 = 契约 `isAbsoluteHostPath` 的上界 `ABSOLUTE_HOST_PATH_MAX`),
|
|
483
483
|
* core 与两个店都不设 root 长度上限 ⇒ 写面全程无阻;而 `DELETE /v1/rules` 的体 schema 此前独立写死
|
|
484
484
|
* `scope: max(1088)`,且 safeParse 排在 `parseRuleScope`/权限判据**之前** ⇒ 超限 root 的规则
|
|
485
485
|
* `GET /v1/rules` **列得出**、按同一串 DELETE **恒 400**。全仓只有一个 `lane.removeRule` 调用点(直删
|
|
@@ -491,7 +491,7 @@ export declare function serializeRuleScope(scope: RuleScope): string;
|
|
|
491
491
|
* 不碰盘 ⇒ **任何宿主上** 1081..4096 字符的注册 cwd 都能铸出这样一条规则。
|
|
492
492
|
*
|
|
493
493
|
* 取值 = `"project:"` 前缀 + 铸侧 root 的入口尺。`path.resolve` 对「已绝对、无 `..` 段」的路径不加长
|
|
494
|
-
* (`
|
|
494
|
+
* (`isAbsoluteHostPath` 两条都要求),故 `ABSOLUTE_HOST_PATH_MAX` 是 root 的真上界。**两侧从此同源**:谁改铸侧
|
|
495
495
|
* 的尺,撤侧自动跟。
|
|
496
496
|
*/
|
|
497
497
|
export declare const MAX_RULE_SCOPE_CHARS: number;
|
package/dist/rules-consent.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
4
4
|
import { createLogger } from "./observability/logger.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ABSOLUTE_HOST_PATH_MAX } from "@sema-agent/settings-schema";
|
|
6
6
|
import { isDirectWritableRuleBehavior } from "./permission-rule-vocab.js";
|
|
7
7
|
import { CONTROL_AND_BIDI_CHARS, hasBidiControls } from "./text-bidi.js";
|
|
8
8
|
import { confirmRuleApproval, RULE_BEHAVIORS, parseRuleText, ruleReachesProgramRun, ruleToolGrammarOf, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, addPersistedRule, removePersistedRule, isValidDurableScope, effectiveOrThrow, readRowBehavior, } from "@sema-agent/core";
|
|
@@ -51,7 +51,7 @@ export function serializeRuleScope(scope) {
|
|
|
51
51
|
throw new Error("serializeRuleScope got a session scope — session consent never lands on the durable rules wire (design/382 §4.3 two-member durable face); a session row reaching this serializer is a defect upstream, not a spelling to invent");
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
export const MAX_RULE_SCOPE_CHARS = "project:".length +
|
|
54
|
+
export const MAX_RULE_SCOPE_CHARS = "project:".length + ABSOLUTE_HOST_PATH_MAX;
|
|
55
55
|
export function parseRuleScope(text) {
|
|
56
56
|
if (text === "global")
|
|
57
57
|
return { kind: "global" };
|
package/dist/run-local.js
CHANGED
|
@@ -18,11 +18,13 @@ import { applyRuntimeGovernance, autonomyModeFloor, entryPermissionMode } from "
|
|
|
18
18
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
19
19
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
20
20
|
import { applyCatalogToSource } from "./capabilities/center-prompts.js";
|
|
21
|
-
import {
|
|
21
|
+
import { CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
|
|
22
22
|
import { buildUnpricedCeilingNotice, loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./config.js";
|
|
23
23
|
import { buildStoreOpenRefusalTail } from "./plugins/store-open-refusal.js";
|
|
24
24
|
import { acquireLocalBackendWithWait } from "./plugins/store-lock-wait.js";
|
|
25
25
|
import { configLocalRoot, configProviderKind, createConfigProvider } from "./config-provider.js";
|
|
26
|
+
import { CONFIG_DOMAIN_INVALID_NOTE, buildCandidateRejectedFields, judgeConfigCandidate } from "./web-admin-client/candidate-gate.js";
|
|
27
|
+
import { buildUnreadConfigFileFields } from "./web-admin-client/read-warnings.js";
|
|
26
28
|
import { composeExecutionLane } from "./boot/execution-lane.js";
|
|
27
29
|
import { judgeConsolidationValve } from "./boot/memory-consolidation.js";
|
|
28
30
|
import { logConfigDirMigration, migrateLocalConfigDirectory } from "./config-d-migration.js";
|
|
@@ -330,32 +332,43 @@ export async function runLocal(argv, deps = {}) {
|
|
|
330
332
|
provisionSemaConfigDir(config, logger);
|
|
331
333
|
installOutboundDispatcher(logger);
|
|
332
334
|
const providerInput = { provider: config.configProvider ?? "local", localDir: root };
|
|
335
|
+
const configSource = configProviderKind(providerInput);
|
|
336
|
+
if (configSource === "local")
|
|
337
|
+
logConfigDirMigration(logger, await migrateLocalConfigDirectory(root));
|
|
333
338
|
try {
|
|
334
|
-
await composeExecutionLane(config, { configSource
|
|
339
|
+
await composeExecutionLane(config, { configSource, localRoot: root, logger, drivesFromFile: ["host"] });
|
|
335
340
|
}
|
|
336
341
|
catch (err) {
|
|
337
342
|
printErr(err instanceof Error ? err.message : String(err));
|
|
338
343
|
return 2;
|
|
339
344
|
}
|
|
340
345
|
const configProvider = createConfigProvider(providerInput);
|
|
341
|
-
if (configProvider.kind === "local")
|
|
342
|
-
logConfigDirMigration(logger, await migrateLocalConfigDirectory(root));
|
|
343
346
|
let effective;
|
|
347
|
+
let candidatePrompts;
|
|
344
348
|
try {
|
|
345
349
|
const r = await configProvider.fetchEffective(undefined);
|
|
346
350
|
if (r) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
351
|
+
const verdict = judgeConfigCandidate(r);
|
|
352
|
+
if (verdict.verdict === "rejected") {
|
|
353
|
+
for (const de of r.domainErrors ?? [])
|
|
354
|
+
logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: CONFIG_DOMAIN_INVALID_NOTE });
|
|
355
|
+
logger.warn("config_candidate_rejected", buildCandidateRejectedFields(r, verdict, "run-local", { lkgVersion: "(env)", ...(configProvider.kind === "local" ? { dir: root } : {}) }));
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
for (const f of r.unreadFiles ?? [])
|
|
359
|
+
logger.warn("config_unread_file", buildUnreadConfigFileFields(f, configProvider.kind === "local" ? root : undefined));
|
|
360
|
+
for (const d of r.droppedEntries ?? [])
|
|
361
|
+
logger.warn("config_entry_dropped", { domain: d.domain, entry: d.entry, reasonCode: d.reasonCode, error: d.error, version: r.effective.version, phase: "boot", note: "this one entry failed its domain schema and was dropped on its own — the rest of the candidate applies (per-entry tolerance); fix the named entry to bring it back" });
|
|
362
|
+
effective = r.effective;
|
|
363
|
+
candidatePrompts = verdict.prompts;
|
|
364
|
+
applyEffective(config, r.effective, logger, { leg: "boot" });
|
|
365
|
+
if (effective?.mcp)
|
|
366
|
+
config.mcpServers = resolveMcpServers(effective.mcp, logger);
|
|
367
|
+
if (effective?.a2a)
|
|
368
|
+
config.a2aPeers = resolveA2aPeers(effective.a2a, logger);
|
|
369
|
+
applyCenterReadFace(config, r.effective, logger, { phase: "boot" });
|
|
370
|
+
logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version });
|
|
371
|
+
}
|
|
359
372
|
}
|
|
360
373
|
}
|
|
361
374
|
catch (err) {
|
|
@@ -470,13 +483,8 @@ export async function runLocal(argv, deps = {}) {
|
|
|
470
483
|
})();
|
|
471
484
|
try {
|
|
472
485
|
await promptSource.restore();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const pv = validatePromptsDomain(promptsRaw);
|
|
476
|
-
if (pv.ok)
|
|
477
|
-
await applyCatalogToSource(promptSource, pv.value, undefined, logger, "run-local");
|
|
478
|
-
else
|
|
479
|
-
logger.warn("center_prompts_invalid", { phase: "run-local", error: pv.error, note: "malformed prompts face — built-ins serve" });
|
|
486
|
+
if (candidatePrompts !== undefined) {
|
|
487
|
+
await applyCatalogToSource(promptSource, candidatePrompts, undefined, logger, "run-local");
|
|
480
488
|
}
|
|
481
489
|
else {
|
|
482
490
|
await applyCatalogToSource(promptSource, undefined, undefined, logger, "run-local");
|
package/dist/runs.d.ts
CHANGED
|
@@ -116,6 +116,15 @@ export declare function resumeAtHttpStatus(result: {
|
|
|
116
116
|
* `resumeAtHttpStatus` 自己(单元测试直接喂码)。
|
|
117
117
|
*/
|
|
118
118
|
export declare function syncRefusalHttpStatus(result: Pick<TaskResult, "terminal" | "stats">): number | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* 一条提交腿把引擎终局落行之后的**唯一**收尾:**零工作的拒不占提交幂等键**。判据就是上面这只 {@link syncRefusalHttpStatus}
|
|
121
|
+
* (零 turn、零 token 且是 prepare 期的拒码)—— 三条提交腿(同步 / 流式 / durable)同调这一只,不另开判据。
|
|
122
|
+
* 行留作那次拒绝的记录,只放开键:拒绝邀请的正是重发(同步腿的 4xx / 带 `Retry-After` 的 503;流式 / durable 腿结果里的
|
|
123
|
+
* `retryAfterMs`),同键重发必须按新请求受理,而不是回放这次拒绝。做过工作的结局(完成 / 失败 / 取消 / park)一律占键。
|
|
124
|
+
*/
|
|
125
|
+
export declare function releaseKeyIfZeroWorkRefusal(runStore: {
|
|
126
|
+
releaseIdemKey(taskId: string): Promise<void>;
|
|
127
|
+
}, taskId: string, result: Pick<TaskResult, "terminal" | "stats">): Promise<void>;
|
|
119
128
|
/**
|
|
120
129
|
* E18 resume-at — per-turn anchor capture, shared by runInBackground AND the resume-leg driveResume so the (single,
|
|
121
130
|
* tricky) capture rule can't drift between the two stream-drain loops.
|
package/dist/runs.js
CHANGED
|
@@ -111,6 +111,10 @@ export function syncRefusalHttpStatus(result) {
|
|
|
111
111
|
return undefined;
|
|
112
112
|
return resumeAtHttpStatus(result);
|
|
113
113
|
}
|
|
114
|
+
export async function releaseKeyIfZeroWorkRefusal(runStore, taskId, result) {
|
|
115
|
+
if (syncRefusalHttpStatus(result) !== undefined)
|
|
116
|
+
await runStore.releaseIdemKey(taskId);
|
|
117
|
+
}
|
|
114
118
|
export class TurnAnchorCapture {
|
|
115
119
|
capture;
|
|
116
120
|
onCaptureFail;
|
|
@@ -340,6 +344,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
340
344
|
reached = { kind: "done", result: safe };
|
|
341
345
|
const plane = outwardPlane(safe.terminal);
|
|
342
346
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
347
|
+
await releaseKeyIfZeroWorkRefusal(runStore, taskId, safe);
|
|
343
348
|
recordDone(safe, plane.status);
|
|
344
349
|
return;
|
|
345
350
|
}
|
|
@@ -353,6 +358,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
353
358
|
reached = { kind: "done", result: safe };
|
|
354
359
|
const plane = outwardPlane(safe.terminal);
|
|
355
360
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
361
|
+
await releaseKeyIfZeroWorkRefusal(runStore, taskId, safe);
|
|
356
362
|
recordDone(safe, plane.status);
|
|
357
363
|
return;
|
|
358
364
|
}
|
|
@@ -408,6 +414,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
408
414
|
reached = { kind: "done", result: safe };
|
|
409
415
|
const plane = outwardPlane(safe.terminal);
|
|
410
416
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
417
|
+
await releaseKeyIfZeroWorkRefusal(runStore, taskId, safe);
|
|
411
418
|
recordDone(safe, failureCodeOf(safe.terminal) === CANCELLED_CODE ? CANCELLED_CODE : plane.status);
|
|
412
419
|
}
|
|
413
420
|
continue;
|
|
@@ -482,6 +489,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
482
489
|
const plane = outwardPlane(reached.result.terminal);
|
|
483
490
|
settledAsCancel = failureCodeOf(reached.result.terminal) === CANCELLED_CODE;
|
|
484
491
|
await runStore.setTerminal(taskId, plane.status, reached.result, plane.errorMessage ?? null);
|
|
492
|
+
await releaseKeyIfZeroWorkRefusal(runStore, taskId, reached.result);
|
|
485
493
|
}
|
|
486
494
|
else if (reached?.kind === "suspended") {
|
|
487
495
|
await runStore.setSuspended(taskId, reached.result);
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
* name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
|
|
25
25
|
*/
|
|
26
26
|
import { type PermissionMode, type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
27
|
-
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
27
|
+
import { AUTONOMY_MODES, type GovernanceConfig } from "@sema-agent/settings-schema";
|
|
28
28
|
import { type GovernanceAskMarks } from "./governance-ask-marks.js";
|
|
29
29
|
import { type GovernanceOverrides } from "./governance-overrides.js";
|
|
30
|
-
/** 运维 autonomy 四档的**闭集**(
|
|
31
|
-
export
|
|
30
|
+
/** 运维 autonomy 四档的**闭集**(契约成员表本尊,照旧从本模块转出)。`config.ts` 的 `parseAutonomy` 也从这里读 —— 四档词表全仓一份。 */
|
|
31
|
+
export { AUTONOMY_MODES };
|
|
32
32
|
/** A per-command governance rule (registry `runtime.commandPolicy[]`) —— **型也从契约派生**:
|
|
33
33
|
* 契约给 `command` 加一条 refine / 给 `decision` 加一个判词,本仓的型当场跟上(或在下面的
|
|
34
34
|
* `RANK` / 穷尽 switch 上编译红),而不是靠人记得把手写联合改一遍。 */
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { clampPermissionMode, combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, modePolicyOf, parseLeadingCommandName, wrapToolPolicy, } from "@sema-agent/core";
|
|
2
|
-
import {
|
|
2
|
+
import { AUTONOMY_MODES, COMMAND_DECISIONS } from "@sema-agent/settings-schema";
|
|
3
3
|
import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
|
|
4
4
|
import { tightenGoverned } from "./governance-overrides.js";
|
|
5
5
|
import { createDeploymentAuthoredPolicy, operatorAsk } from "./operator-ask.js";
|
|
6
6
|
import { heldRuleName } from "./tool-rule-names.js";
|
|
7
|
-
|
|
8
|
-
const COMMAND_DECISIONS = COMMAND_RULE_SHAPE.decision.options;
|
|
9
|
-
export const AUTONOMY_MODES = GovernanceConfig.shape.autonomy.unwrap().options;
|
|
7
|
+
export { AUTONOMY_MODES };
|
|
10
8
|
const COARSE_SHELL_TOOLS = CORE_COARSE_SHELL_TOOLS;
|
|
11
9
|
export const COARSE_SHELL_TOOLS_MIRROR = COARSE_SHELL_TOOLS;
|
|
12
10
|
function createCommandAskListPolicy(askCommands, tools = COARSE_SHELL_TOOLS) {
|
package/dist/security.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { type SessionStore, type SessionTreeEntry } from "@sema-agent/core";
|
|
|
3
3
|
import type { ServiceConfig } from "./config-types.js";
|
|
4
4
|
import type { TaskRequestBody } from "./http/wire-types.js";
|
|
5
5
|
import type { StagingHandle } from "./session-sync-kernel.js";
|
|
6
|
-
import type { SessionFaceReader } from "./session-incarnation.js";
|
|
6
|
+
import type { SessionFace, SessionFaceReader } from "./session-incarnation.js";
|
|
7
|
+
import type { SessionRowPatch } from "./session-row.js";
|
|
7
8
|
/** One row of the session-list projection (CC /resume picker) — the durable session abstraction's enumeration
|
|
8
9
|
* output (§0.5). Mirrors {@link RunStore}'s shipped `SessionSummary` shape so the wire response is unchanged. */
|
|
9
10
|
export interface SessionListItem {
|
|
@@ -23,8 +24,12 @@ export interface SessionListItem {
|
|
|
23
24
|
* 是两条身份轴,命名对齐 core `SessionStoreSummary.lastTaskRunId`。wire:additive 键零投影透传
|
|
24
25
|
* (sessions-list.ts);sema-sdk spec `SessionSummary` 闭集候 SDK 半场开键([ref]/.82 班车)。 */
|
|
25
26
|
lastTaskRunId?: string;
|
|
26
|
-
/**
|
|
27
|
+
/** The session's title — the user's (`PATCH /v1/sessions/:id`) or, until the user sets one, the auto-titler's
|
|
28
|
+
* (null = untitled / lister face has no session_meta). One column, two writers, one order: `session-row.ts`. */
|
|
27
29
|
title: string | null;
|
|
30
|
+
/** Pinned by the user (`PATCH /v1/sessions/:id`). ALWAYS present — `false` by default (a session's state, not a capability);
|
|
31
|
+
* a lister face with no session_meta answers `false` (nothing there can pin it). */
|
|
32
|
+
pinned: boolean;
|
|
28
33
|
}
|
|
29
34
|
/** A SessionStore that may additionally expose tenant-ownership ops (the TiDB store does) AND the
|
|
30
35
|
* `SessionRepo` enumeration/lifecycle seam (§0.5: list/fork/delete homed on the session abstraction's own
|
|
@@ -115,6 +120,18 @@ export type OwnerAwareSessionStore = SessionStore & {
|
|
|
115
120
|
limit: number;
|
|
116
121
|
q?: string;
|
|
117
122
|
}) => Promise<SessionListItem[]>;
|
|
123
|
+
/** ONE session's list row — the SAME projection {@link listSessions} emits for it (same columns, same mapper). `undefined` = not a
|
|
124
|
+
* row of the list face (no such session, or a placed / delegation-transcript session, which never lists). Reader: the 2c
|
|
125
|
+
* manifest (`title` / `pinned`). */
|
|
126
|
+
sessionSummaryOf?: (sessionId: string) => Promise<SessionListItem | undefined>;
|
|
127
|
+
/** The session row's writable bits (`title` / `pinned`) — the ONLY writer of a user-set title or pin (`title: null` clears it back
|
|
128
|
+
* to the automatic title; the auto-titler's `setTitleIfNull` fills a NULL title only; the law of the two writers is written in
|
|
129
|
+
* `session-row.ts`). **Written on the occupant the caller verified, and only on it**: `face` is the {@link sessionFaceOf} read
|
|
130
|
+
* the caller's gate judged; the write and the read-back are both conditioned on that occupant's incarnation (the same law as
|
|
131
|
+
* the parked decide leg's two reads, `session-incarnation.ts`). Answers that occupant's list row after the write
|
|
132
|
+
* ({@link sessionSummaryOf}'s projection); `undefined` = no list row for that occupant (deleted, re-registered by anyone in the
|
|
133
|
+
* meantime, or a placed session) — nothing was written. A failed write throws. */
|
|
134
|
+
patchSessionMeta?: (sessionId: string, patch: SessionRowPatch, face: SessionFace) => Promise<SessionListItem | undefined>;
|
|
118
135
|
/** E17 — fork a session's whole history to a NEW session owned by `owner`; null if the source is unknown. */
|
|
119
136
|
fork?: (sourceId: string, owner: string | null) => Promise<string | null>;
|
|
120
137
|
/** E18 — the session's current leaf `SessionTreeEntry.id` (a cache-bypassing single read), so the resume-at
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话行的**可写位**(`title` / `pinned`)—— `PATCH /v1/sessions/:id` 的体闭集、两键的判据,与 2c 导入同读的那一套(唯一属主)。
|
|
3
|
+
*
|
|
4
|
+
* ## 一列一事实,写者次序成文
|
|
5
|
+
* · `title` 是一列(SQL `session_meta.title` / local 标题边档),两个写者,一条律:**用户**经会话店唯一写口 `patchSessionMeta`
|
|
6
|
+
* **无条件**写 —— 写一个串 = 改名,写 `null` = **清回自动标题**(列回到 NULL);**自动标题器**经 `setTitleIfNull` **只填 NULL**
|
|
7
|
+
* (每次提交按列读一次:列是 NULL 才起名,见 `session-titler.ts`)。所以清与填是同一列同一律:用户清掉之后,下一次提交由自动标题器
|
|
8
|
+
* 照常填;用户写过串的会话,自动标题器永不覆盖(无条件写盖得掉自动标题,NULL 守卫盖不掉用户标题)。没有第二列(不另设 `userTitle` /
|
|
9
|
+
* `label`)。清的拼法只有 `null` 一种 —— 空串 `""` 是坏体(`request.body_shape`),不是「清」。
|
|
10
|
+
* · `pinned` 是一列布尔,缺省 `false`;列表行**恒在场**(置顶是会话的状态,不是能力)。
|
|
11
|
+
*
|
|
12
|
+
* ## 上界 = 列宽
|
|
13
|
+
* {@link SESSION_TITLE_MAX_CHARS} 是 `title` 列的宽度,两方言 DDL 内插的就是这一个数(`tidb-pool.ts` / `pg-session-storage.ts`),
|
|
14
|
+
* wire 上界与存储上界是同一个事实。按**字符**计(Unicode 码点 —— 与 MySQL 协议 utf8mb4 / PostgreSQL 的 `VARCHAR(n)` 同一单位),
|
|
15
|
+
* 不按 UTF-16 位计。自动标题器自己的显示预算(`session-titler.ts` 的 `TITLE_MAX_CHARS`)更短,落在这个上界之内。
|
|
16
|
+
*/
|
|
17
|
+
/** `title` 列的宽度(字符)= 用户标题的上界。两方言 DDL 内插它。 */
|
|
18
|
+
export declare const SESSION_TITLE_MAX_CHARS = 120;
|
|
19
|
+
/** 会话行的可写位(体闭集;两键至少一键)。会话店唯一写口 `patchSessionMeta` 的形参。`title: null` = 清回自动标题(列写 NULL)。 */
|
|
20
|
+
export interface SessionRowPatch {
|
|
21
|
+
readonly title?: string | null;
|
|
22
|
+
readonly pinned?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** 一条已分类的拒:码 + 不回显输入的句子。 */
|
|
25
|
+
export interface SessionRowProblem {
|
|
26
|
+
readonly code: "request.body_shape" | "request.field_invalid";
|
|
27
|
+
readonly message: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `title` 一格的**唯一**读者(PATCH 体与 2c 导入同读)。`null` ⇒ 清回自动标题;串 ⇒ 1..{@link SESSION_TITLE_MAX_CHARS} 个字符、首尾无空白、
|
|
31
|
+
* 不含 C0 控制字符({@link C0_CONTROL}),存原文(不脱敏、不改写);空串 ⇒ `request.body_shape`(清的拼法只有 `null`);其余 ⇒ `request.field_invalid`。
|
|
32
|
+
*/
|
|
33
|
+
export declare function readSessionTitle(v: unknown): {
|
|
34
|
+
readonly title: string | null;
|
|
35
|
+
} | {
|
|
36
|
+
readonly problem: SessionRowProblem;
|
|
37
|
+
};
|
|
38
|
+
/** `pinned` 一格的**唯一**读者(PATCH 体与 2c 导入同读)。 */
|
|
39
|
+
export declare function readSessionPinned(v: unknown): {
|
|
40
|
+
readonly pinned: boolean;
|
|
41
|
+
} | {
|
|
42
|
+
readonly problem: SessionRowProblem;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* `PATCH /v1/sessions/:id` 体的**唯一**读者。形不对(非对象 / 空 / 表外键)⇒ `request.body_shape`;两键各走自己那一格的读者。
|
|
46
|
+
* 拒句不回显调用方的键名或值。
|
|
47
|
+
*/
|
|
48
|
+
export declare function readSessionRowPatch(body: unknown): {
|
|
49
|
+
readonly patch: SessionRowPatch;
|
|
50
|
+
} | {
|
|
51
|
+
readonly problem: SessionRowProblem;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=session-row.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const SESSION_TITLE_MAX_CHARS = 120;
|
|
2
|
+
const SESSION_ROW_PATCH_KEYS = { title: true, pinned: true };
|
|
3
|
+
const BODY_SHAPE_MESSAGE = "the body must be a JSON object carrying at least one of `title` and `pinned`, and nothing else";
|
|
4
|
+
const EMPTY_TITLE_MESSAGE = "title must not be the empty string — send `null` to clear it back to the automatic title";
|
|
5
|
+
const C0_CONTROL = /[\u0000-\u001f]/u;
|
|
6
|
+
export function readSessionTitle(v) {
|
|
7
|
+
if (v === null)
|
|
8
|
+
return { title: null };
|
|
9
|
+
if (v === "")
|
|
10
|
+
return { problem: { code: "request.body_shape", message: EMPTY_TITLE_MESSAGE } };
|
|
11
|
+
if (typeof v !== "string")
|
|
12
|
+
return { problem: { code: "request.field_invalid", message: "title must be a string, or null to clear it" } };
|
|
13
|
+
if (Array.from(v).length > SESSION_TITLE_MAX_CHARS)
|
|
14
|
+
return { problem: { code: "request.field_invalid", message: `title must be 1 to ${SESSION_TITLE_MAX_CHARS} characters` } };
|
|
15
|
+
if (v !== v.trim())
|
|
16
|
+
return { problem: { code: "request.field_invalid", message: "title must not start or end with whitespace" } };
|
|
17
|
+
if (C0_CONTROL.test(v))
|
|
18
|
+
return { problem: { code: "request.field_invalid", message: "title must not contain control characters (U+0000 to U+001F)" } };
|
|
19
|
+
return { title: v };
|
|
20
|
+
}
|
|
21
|
+
export function readSessionPinned(v) {
|
|
22
|
+
return typeof v === "boolean" ? { pinned: v } : { problem: { code: "request.field_invalid", message: "pinned must be a boolean" } };
|
|
23
|
+
}
|
|
24
|
+
export function readSessionRowPatch(body) {
|
|
25
|
+
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
26
|
+
return { problem: { code: "request.body_shape", message: BODY_SHAPE_MESSAGE } };
|
|
27
|
+
const keys = Object.keys(body);
|
|
28
|
+
if (keys.length === 0 || keys.some((k) => !Object.hasOwn(SESSION_ROW_PATCH_KEYS, k))) {
|
|
29
|
+
return { problem: { code: "request.body_shape", message: BODY_SHAPE_MESSAGE } };
|
|
30
|
+
}
|
|
31
|
+
const b = body;
|
|
32
|
+
let patch = {};
|
|
33
|
+
if ("title" in b) {
|
|
34
|
+
const read = readSessionTitle(b.title);
|
|
35
|
+
if ("problem" in read)
|
|
36
|
+
return read;
|
|
37
|
+
patch = { ...patch, title: read.title };
|
|
38
|
+
}
|
|
39
|
+
if ("pinned" in b) {
|
|
40
|
+
const read = readSessionPinned(b.pinned);
|
|
41
|
+
if ("problem" in read)
|
|
42
|
+
return read;
|
|
43
|
+
patch = { ...patch, pinned: read.pinned };
|
|
44
|
+
}
|
|
45
|
+
return { patch };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=session-row.js.map
|
package/dist/session-sync.d.ts
CHANGED
|
@@ -127,6 +127,12 @@ export interface SessionManifest {
|
|
|
127
127
|
entryId: string;
|
|
128
128
|
owner: string | null;
|
|
129
129
|
}>;
|
|
130
|
+
/** The session row's writable bits (`src/session-row.ts`), read through the list projection (`sessionSummaryOf`): the title
|
|
131
|
+
* (`null` = no title — the PUSH that replays it clears the destination back to automatic, the same law as `PATCH`) and the pin.
|
|
132
|
+
* Present iff the session is a row of the list face — every ordinary session; a placed (delegation-transcript) session has no
|
|
133
|
+
* writable row bits, so both keys are absent there. */
|
|
134
|
+
title?: string | null;
|
|
135
|
+
pinned?: boolean;
|
|
130
136
|
}
|
|
131
137
|
/**
|
|
132
138
|
* P1d-α (PULL streaming, §15) — EXPORT a session's portable state as a {@link SessionManifest} (entries LIFTED OFF
|
package/dist/session-sync.js
CHANGED
|
@@ -40,10 +40,14 @@ export async function exportSessionManifest(sessionId, srcBackend, historyStore)
|
|
|
40
40
|
: [];
|
|
41
41
|
const anchorStore = srcBackend.resumeAnchor();
|
|
42
42
|
const anchors = await anchorStore.listBySession(sessionId);
|
|
43
|
+
const summaryOf = session.sessionSummaryOf?.bind(session);
|
|
44
|
+
if (summaryOf === undefined)
|
|
45
|
+
throw new Error("exportSessionManifest: the session store has no row read face (sessionSummaryOf) — the sessionSync fact should have refused this deployment");
|
|
46
|
+
const row = await summaryOf(sessionId);
|
|
43
47
|
const getBlobFn = fs.getBlob?.bind(fs);
|
|
44
48
|
const getBlob = getBlobFn ? (hash) => getBlobFn(hash) : async () => undefined;
|
|
45
49
|
return {
|
|
46
|
-
manifest: { sessionId, entryIds, entryCount: entryIds.length, leafId, fileHistory, policy, anchors },
|
|
50
|
+
manifest: { sessionId, entryIds, entryCount: entryIds.length, leafId, fileHistory, policy, anchors, ...(row !== undefined ? { title: row.title, pinned: row.pinned } : {}) },
|
|
47
51
|
getBlob,
|
|
48
52
|
};
|
|
49
53
|
}
|
package/dist/session-titler.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated session titles.
|
|
3
3
|
*
|
|
4
|
-
* SHAPE: fire-and-forget at
|
|
5
|
-
* for the run to finish, and the web Recents gets the title sooner).
|
|
6
|
-
* `WHERE title IS NULL`) — the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* SHAPE: fire-and-forget at a task submit whose session has NO title (the objective is known then — no need to wait
|
|
5
|
+
* for the run to finish, and the web Recents gets the title sooner). Fills a NULL title only (`setTitleIfNull`,
|
|
6
|
+
* store-side `WHERE title IS NULL`) — the user writes the SAME column unconditionally (`PATCH /v1/sessions/:id` →
|
|
7
|
+
* `patchSessionMeta`): a user-set title is never overwritten (the probe below reads "titled" and skips the model
|
|
8
|
+
* call), and a title the user cleared (`title: null` ⇒ NULL) is filled again at the next submit (law: `session-row.ts`).
|
|
9
|
+
* "Has it a title?" is read from the column on every submit (one point read) — the process keeps NO "already titled"
|
|
10
|
+
* memo, because such a memo would be a second answer to that question that a clear turns stale. Generation = one
|
|
11
|
+
* cheap-model one-liner via the deployment's hookLlm face (summarize role → MODEL_CHEAP_ID when configured, else the
|
|
12
|
+
* main model; bounded + timed out).
|
|
9
13
|
*
|
|
10
14
|
* FAILURE POSTURE: everything is best-effort — a generation/store failure logs and leaves title null (the
|
|
11
15
|
* list falls back to objectivePreview). An in-process pending set dedupes concurrent submits on the same
|
package/dist/session-titler.js
CHANGED
|
@@ -58,6 +58,7 @@ export function createSessionTitler(deps) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
if (state === "titled") {
|
|
61
|
+
seen.delete(sessionId);
|
|
61
62
|
deps.metrics?.inc("session_title_total", { outcome: "already_titled" });
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
@@ -75,11 +76,12 @@ export function createSessionTitler(deps) {
|
|
|
75
76
|
const landed = await deps.setTitleIfNull(sessionId, title);
|
|
76
77
|
if (!landed) {
|
|
77
78
|
const after = await probeOrContinue(sessionId, "none");
|
|
78
|
-
if (after.state
|
|
79
|
+
if (after.state !== "untitled")
|
|
79
80
|
seen.delete(sessionId);
|
|
80
81
|
deps.metrics?.inc("session_title_total", { outcome: after.unreadable ? "probe_failed" : after.state === "none" ? "row_not_yet" : "already_titled" });
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
84
|
+
seen.delete(sessionId);
|
|
83
85
|
deps.metrics?.inc("session_title_total", { outcome: "set" });
|
|
84
86
|
deps.logger?.info?.("session_titled", { sessionId, titleChars: title.length });
|
|
85
87
|
})().catch((e) => {
|
package/dist/spec-fields.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isThinkingLevel } from "@sema-agent/core";
|
|
2
2
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isAbsoluteHostPath } from "@sema-agent/settings-schema";
|
|
4
4
|
import { allowLayerEntryCountIssue, ruleEntryListShapeOk } from "./rule-entry-list.js";
|
|
5
5
|
export function normalizeSuggestNextPrompts(v) {
|
|
6
6
|
if (v === true)
|
|
@@ -88,7 +88,7 @@ const SKILL_FIELD_SHAPES = {
|
|
|
88
88
|
allowedTools: { required: false, ok: skillToolListOk },
|
|
89
89
|
disallowedTools: { required: false, ok: skillToolListOk },
|
|
90
90
|
files: { required: false, ok: skillFilesOk },
|
|
91
|
-
baseDir: { required: false, ok:
|
|
91
|
+
baseDir: { required: false, ok: isAbsoluteHostPath },
|
|
92
92
|
};
|
|
93
93
|
const skillOk = (s) => {
|
|
94
94
|
if (!plainObject(s))
|
package/dist/task-cwd.d.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/** Cap on a cwd path length (defensive — a row/path label bound; a real path is far shorter). */
|
|
2
|
-
export declare const MAX_CWD_CHARS = 4096;
|
|
3
|
-
/** Is this a structurally-valid absolute host path? Rejects: non-strings, relative paths (TOC review #12 — a
|
|
4
|
-
* relative cwd would resolve against the SERVICE process cwd, not the user's project = silent wrong-dir), empty,
|
|
5
|
-
* over-long, and NUL-byte injection. NOT an existence check (the env surfaces a missing dir at first tool use).
|
|
6
|
-
* Defense-in-depth (double-review LOW): `path.isAbsolute` is SYNTACTIC — `/a/../../etc` is "absolute" yet contains
|
|
7
|
-
* `..` traversal that the host env would `path.resolve` away. Single-user host lane = the caller's own machine (no
|
|
8
|
-
* cross-tenant escape), but we reject any path with a `..` segment so the honored cwd is the literal path sent.
|
|
9
|
-
*
|
|
10
|
-
* 🔴 **接缝登记(7.74.0 / core 7.17.0 [ref],今日无可达分歧 —— 登记的是「以后会分歧」)**:这条判断回答的是
|
|
11
|
-
* 「引擎眼里这条 cwd 算不算绝对」,而引擎自本版起按**树的家族**判(`isAbsoluteForFamily(pathFamilyOf({root,cwd}), p)`,
|
|
12
|
-
* 家族从 root/cwd 的**拼法**读,**从不**读 `process.platform`)。本函数用的是 Node 的宿主口径,两者:
|
|
13
|
-
* · **posix 家族逐字同判**(2026-09-12 实测六形 `/etc/passwd` / `a/b` / `\\host\share\x` / `C:\x` /
|
|
14
|
-
* `//host/share/x` / `/c/x` 六格全同)⇒ 今天(Linux 宿主 + posix 树,本位又只在单用户 `host` 车道上场)
|
|
15
|
-
* **零分歧**,不改;
|
|
16
|
-
* · **win 家族分歧在 `/`-开头但没有盘符的那一类**(`/etc/passwd`、`/c/x`):Node 的 win32 口径说「绝对」,
|
|
17
|
-
* 引擎说「rooted 但未完全限定」(要拼上基准的盘/共享根才成一条真路径)。⇒ **哪天 `host` 车道要接 win 树,
|
|
18
|
-
* 先改这里**:把判据换成 core 的 `isAbsoluteForFamily`,并把家族从这条 cwd 自己的拼法取。
|
|
19
|
-
* 钉见 `test/task-cwd.test.ts` 的「[ref] 路径家族接缝」格(实测读数,不是推断)。 */
|
|
20
|
-
export declare function isValidCwd(cwd: unknown): cwd is string;
|
|
21
1
|
/**
|
|
22
2
|
* 词法归一:纯字符串运算,折 `.` / `..` / 重复分隔符,**不碰 fs**。
|
|
23
3
|
* `posix.normalize` 把 `..` 在根部截断(`/../x` → `/x`),与「根之上没有东西」的语义一致;尾部分隔符
|
package/dist/task-cwd.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { isAbsoluteHostPath } from "@sema-agent/settings-schema";
|
|
2
3
|
import { executionLaneOf, handsOnWorkerFilePlane } from "./execution-lane-caps.js";
|
|
3
|
-
export const MAX_CWD_CHARS = 4_096;
|
|
4
|
-
export function isValidCwd(cwd) {
|
|
5
|
-
return (typeof cwd === "string" &&
|
|
6
|
-
cwd.length > 0 &&
|
|
7
|
-
cwd.length <= MAX_CWD_CHARS &&
|
|
8
|
-
!cwd.includes("\0") &&
|
|
9
|
-
path.isAbsolute(cwd) &&
|
|
10
|
-
!cwd.split(/[/\\]/).includes(".."));
|
|
11
|
-
}
|
|
12
4
|
export function normalizeAbsolutePathLexically(p) {
|
|
13
5
|
const collapsed = path.posix.normalize(p.replace(/^\/+/, "/"));
|
|
14
6
|
return collapsed.length > 1 ? collapsed.replace(/\/+$/, "") : collapsed;
|
|
@@ -55,7 +47,7 @@ export function parseAdditionalDirectories(raw) {
|
|
|
55
47
|
for (const entry of raw) {
|
|
56
48
|
if (out.length >= MAX_ADDITIONAL_DIRS)
|
|
57
49
|
break;
|
|
58
|
-
if (!
|
|
50
|
+
if (!isAbsoluteHostPath(entry) || seen.has(entry))
|
|
59
51
|
continue;
|
|
60
52
|
seen.add(entry);
|
|
61
53
|
out.push(entry);
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -446,13 +446,11 @@ export interface ToolApprovalFrame {
|
|
|
446
446
|
* 更需要知道「你看不到的那份里有隐形字符」,所以帽不影响本键;
|
|
447
447
|
* · 序列化失败(循环引用 / 抛错的 `toJSON` / BigInt)⇒ 两件事一起交白卷:args 不上帧,本键**不铸**
|
|
448
448
|
* (两次 `JSON.stringify` 共用同一个 try/catch —— 顶注反对的是第二个**静默降级点**,不是第二次序列化);
|
|
449
|
-
* ·
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* ·
|
|
453
|
-
*
|
|
454
|
-
* 「在哪一位」**、只能按整只 args 打标。这是有意的取舍(夹层密钥的根治要求单一剥点),不是遗漏;
|
|
455
|
-
* 要恢复定位面需要给脱敏器一条 span/偏移表回填路(S-103 定案明确不做),不是在这条腿上加特判。
|
|
449
|
+
* · 素材是**原字节**,不是脱敏后那份:7.70.0–7.124.x 的脱敏器吐出剥掉全部 `\p{Cf}` 的文本(bidi 控制符整族都是
|
|
450
|
+
* `\p{Cf}`),在它上面扫本键恒缺席;7.125.0 起脱敏产物回到原文(认出的凭据跨度映回原文坐标替换,跨度外的
|
|
451
|
+
* 格式字符逐字保留),原字节与卡面在 bidi 控制符上已无差别,素材仍取原字节(脱敏前那一次序列化,上一条)。
|
|
452
|
+
* · **「字节零改」对卡面 args 成立**:帧/卡上的 args 与预览是脱敏产物,方向控制符留在原位 —— 壳拿得到「在哪一位」,
|
|
453
|
+
* 显形 / 中和归壳的渲染面;只有被认出的凭据跨度(连同夹在其中的格式字符)整段换成记号。
|
|
456
454
|
*
|
|
457
455
|
* 判据**不看 `message`**(引擎/策略写的说明文本,不是待执行输入):本键被壳读作「**待执行的输入**可能
|
|
458
456
|
* 在骗你的眼睛」,把两个来源折进一个布尔会让壳无法判断该给哪一段加显形标记。
|