@sema-agent/server 7.148.0 → 7.149.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/MIGRATION.md +429 -0
- package/README.md +16 -1
- package/README.zh-CN.md +4 -0
- package/USAGE.md +89 -55
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +32 -32
- package/dist/approval-leg-origin.d.ts +33 -0
- package/dist/approval-leg-origin.js +4 -0
- package/dist/auth-bridge.d.ts +9 -1
- package/dist/auth-bridge.js +4 -4
- package/dist/auth-keys.d.ts +10 -1
- package/dist/auth-keys.js +8 -0
- package/dist/boot/execution-env.js +10 -11
- package/dist/boot/execution-lane.d.ts +6 -4
- package/dist/boot/execution-lane.js +3 -1
- package/dist/boot/org-delivery.d.ts +3 -2
- package/dist/boot/resolve-spec.js +6 -4
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/stage-01-config.d.ts +1 -0
- package/dist/boot/stage-01-config.js +4 -3
- package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
- package/dist/boot/stage-02-web-admin-pull.js +10 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +1 -0
- package/dist/boot/stage-06-runners.d.ts +2 -1
- package/dist/boot/stage-07-capability-layer.d.ts +2 -1
- package/dist/boot/stage-08-reapers.d.ts +2 -1
- package/dist/boot/stage-09-leader.d.ts +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -1
- package/dist/boot/stores.d.ts +3 -0
- package/dist/boot/stores.js +22 -19
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/capability-facts.d.ts +5 -4
- package/dist/capability-facts.js +3 -14
- package/dist/card-settle-stored-scope.d.ts +56 -0
- package/dist/card-settle-stored-scope.js +27 -0
- package/dist/card-void.d.ts +7 -12
- package/dist/card-void.js +2 -7
- package/dist/config-catalog.d.ts +12 -0
- package/dist/config-catalog.js +36 -32
- package/dist/config-invariants.d.ts +5 -4
- package/dist/config-invariants.js +4 -4
- package/dist/config-types.d.ts +37 -5
- package/dist/config.d.ts +6 -5
- package/dist/config.js +35 -24
- package/dist/context-window-notice.d.ts +31 -0
- package/dist/context-window-notice.js +28 -0
- package/dist/deployment-shape.d.ts +20 -11
- package/dist/deployment-shape.js +1 -0
- package/dist/env-name-allowlist-knobs.d.ts +6 -5
- package/dist/env-name-allowlist-knobs.js +5 -0
- package/dist/execution-lane-caps.d.ts +20 -5
- package/dist/execution-lane-caps.js +18 -4
- package/dist/hooks/command-spawn.d.ts +7 -2
- package/dist/hooks/command-spawn.js +13 -6
- package/dist/hooks/hook-runner.d.ts +5 -0
- package/dist/hooks/hook-runner.js +11 -7
- package/dist/http/admission.js +3 -3
- package/dist/http/approval-sweeps.js +3 -2
- package/dist/http/claim-heartbeat.d.ts +3 -3
- package/dist/http/claim-heartbeat.js +3 -3
- package/dist/http/claimed-run.d.ts +16 -9
- package/dist/http/claimed-run.js +8 -0
- package/dist/http/dispatch.js +5 -3
- package/dist/http/durable-run-leg.d.ts +8 -1
- package/dist/http/durable-run-leg.js +30 -11
- package/dist/http/execution-context.d.ts +115 -14
- package/dist/http/execution-context.js +130 -40
- package/dist/http/owner-access.d.ts +3 -2
- package/dist/http/owner-access.js +2 -3
- package/dist/http/principal-gate.d.ts +18 -11
- package/dist/http/principal-gate.js +3 -1
- package/dist/http/resume-ctx-read.d.ts +14 -3
- package/dist/http/resume-ctx-read.js +8 -2
- package/dist/http/resume-legs.d.ts +35 -3
- package/dist/http/resume-legs.js +244 -92
- package/dist/http/route-ctx.d.ts +6 -0
- package/dist/http/routes/a2a-serve.js +2 -0
- package/dist/http/routes/approvals-assistant.js +3 -2
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -1
- package/dist/http/routes/diagnostics.js +30 -5
- package/dist/http/routes/principal-inbox.d.ts +3 -1
- package/dist/http/routes/principal-inbox.js +3 -2
- package/dist/http/routes/runs.js +10 -7
- package/dist/http/routes/session-sync.d.ts +24 -5
- package/dist/http/routes/session-sync.js +46 -34
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/tasks.js +127 -93
- package/dist/http/server.d.ts +4 -5
- package/dist/http/server.js +30 -5
- package/dist/http/submit-ctx.d.ts +1 -0
- package/dist/http/submit-ctx.js +2 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/leg-facts.d.ts +7 -2
- package/dist/leg-facts.js +1 -0
- package/dist/local-store-declaration.d.ts +54 -0
- package/dist/local-store-declaration.js +59 -0
- package/dist/mcp-assertion-header.d.ts +1 -1
- package/dist/mcp-assertion-header.js +1 -1
- package/dist/memory-project-layer.js +1 -1
- package/dist/memory-scope.d.ts +4 -4
- package/dist/memory-scope.js +4 -4
- package/dist/memory-sync-client.js +2 -2
- package/dist/model-context-window.d.ts +50 -11
- package/dist/model-context-window.js +20 -1
- package/dist/model-entry-refusal.d.ts +5 -4
- package/dist/model-entry-refusal.js +17 -3
- package/dist/model-reach-scope.d.ts +2 -1
- package/dist/observability/fail-open.d.ts +14 -2
- package/dist/observability/fail-open.js +14 -2
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/org-delivery-gate.d.ts +31 -6
- package/dist/org-delivery-gate.js +7 -4
- package/dist/owner-faces.d.ts +3 -2
- package/dist/park-row-commit.d.ts +67 -0
- package/dist/park-row-commit.js +76 -0
- package/dist/parked-decide.d.ts +9 -7
- package/dist/parked-decide.js +25 -6
- package/dist/peer-directory.d.ts +1 -1
- package/dist/plugins/adoption-log-sql.d.ts +3 -3
- package/dist/plugins/adoption-log-sql.js +19 -19
- package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
- package/dist/plugins/approval-ask-store-sql.js +3 -5
- package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
- package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
- package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
- package/dist/plugins/approval-exemption-skill-m46.js +6 -10
- package/dist/plugins/approval-exemption-store.d.ts +26 -1
- package/dist/plugins/approval-exemption-store.js +28 -19
- package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
- package/dist/plugins/ensure-schema-lock-policy.js +75 -9
- package/dist/plugins/file-run-store.d.ts +22 -9
- package/dist/plugins/file-run-store.js +50 -22
- package/dist/plugins/local-checkpoint-store.d.ts +4 -0
- package/dist/plugins/local-checkpoint-store.js +2 -1
- package/dist/plugins/local-data-root-admission.d.ts +52 -0
- package/dist/plugins/local-data-root-admission.js +100 -0
- package/dist/plugins/local-engine-high-water.d.ts +28 -7
- package/dist/plugins/local-engine-high-water.js +27 -22
- package/dist/plugins/local-session-store.d.ts +134 -49
- package/dist/plugins/local-session-store.js +149 -108
- package/dist/plugins/memory-run-store.d.ts +14 -9
- package/dist/plugins/memory-run-store.js +47 -21
- package/dist/plugins/one-time-migration-marker.d.ts +19 -3
- package/dist/plugins/one-time-migration-marker.js +7 -1
- package/dist/plugins/one-time-migrations.d.ts +46 -20
- package/dist/plugins/one-time-migrations.js +102 -79
- package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
- package/dist/plugins/org-delivery-quota-store.js +29 -14
- package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
- package/dist/plugins/permission-rule-store-sql.js +5 -9
- package/dist/plugins/pg-pool.d.ts +1 -1
- package/dist/plugins/pg-pool.js +10 -12
- package/dist/plugins/remote-env-device.d.ts +3 -2
- package/dist/plugins/remote-env-e2b.d.ts +1 -7
- package/dist/plugins/remote-env-e2b.js +10 -7
- package/dist/plugins/remote-env-host.d.ts +50 -9
- package/dist/plugins/remote-env-host.js +77 -20
- package/dist/plugins/remote-env-k8s.js +4 -2
- package/dist/plugins/remote-env-local-docker.d.ts +6 -3
- package/dist/plugins/remote-env-local-docker.js +8 -23
- package/dist/plugins/run-store-sql.d.ts +26 -7
- package/dist/plugins/run-store-sql.js +44 -16
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +3 -1
- package/dist/plugins/scenario-prefix-switch-m42.js +4 -3
- package/dist/plugins/schema-consistency.d.ts +89 -5
- package/dist/plugins/schema-consistency.js +176 -7
- package/dist/plugins/schema-drift-sentence.d.ts +6 -5
- package/dist/plugins/schema-drift-sentence.js +13 -5
- package/dist/plugins/session-placement.d.ts +5 -4
- package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
- package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
- package/dist/plugins/stop-then-start-sentence.d.ts +16 -0
- package/dist/plugins/stop-then-start-sentence.js +7 -0
- package/dist/plugins/store-backend.d.ts +22 -8
- package/dist/plugins/store-backend.js +58 -24
- package/dist/plugins/store-contracts.d.ts +33 -3
- package/dist/plugins/store-contracts.js +16 -0
- package/dist/plugins/tidb-pool.d.ts +11 -6
- package/dist/plugins/tidb-pool.js +18 -12
- package/dist/plugins/workspace-custody.d.ts +44 -0
- package/dist/plugins/workspace-custody.js +47 -0
- package/dist/project-identity.d.ts +8 -7
- package/dist/project-identity.js +40 -17
- package/dist/project-layer-key.d.ts +13 -2
- package/dist/project-layer-key.js +9 -1
- package/dist/read-deny-floor-notice.d.ts +36 -0
- package/dist/read-deny-floor-notice.js +40 -0
- package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
- package/dist/request-declaration-disposition.js +39 -0
- package/dist/run-cancel-card.d.ts +28 -0
- package/dist/run-cancel-card.js +19 -0
- package/dist/run-cancel-context.d.ts +10 -0
- package/dist/run-cancel-context.js +1 -0
- package/dist/run-local.js +28 -13
- package/dist/runs.d.ts +31 -7
- package/dist/runs.js +34 -16
- package/dist/security.d.ts +2 -2
- package/dist/server-secret-env.d.ts +34 -20
- package/dist/server-secret-env.js +61 -18
- package/dist/session-agent.d.ts +3 -2
- package/dist/session-children-retire.d.ts +6 -3
- package/dist/session-children-retire.js +2 -5
- package/dist/session-workspace.d.ts +3 -3
- package/dist/store-admission.d.ts +33 -0
- package/dist/store-admission.js +49 -0
- package/dist/store-posture.d.ts +5 -0
- package/dist/store-posture.js +18 -1
- package/dist/stores-sql/checkpoint-store-sql.d.ts +1 -1
- package/dist/stores-sql/checkpoint-store-sql.js +3 -5
- package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
- package/dist/stores-sql/file-history-store-sql.js +3 -5
- package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.js +4 -6
- package/dist/task-a2a.d.ts +3 -8
- package/dist/task-a2a.js +25 -27
- package/dist/task-cwd.d.ts +23 -7
- package/dist/task-cwd.js +3 -0
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-mcp.js +1 -1
- package/dist/task-settings.js +1 -0
- package/dist/terminal.d.ts +2 -0
- package/dist/terminal.js +21 -6
- package/dist/tool-approval.d.ts +44 -36
- package/dist/tool-approval.js +66 -47
- package/dist/trace/engine-notice-wire.d.ts +24 -21
- package/dist/trace/engine-notice-wire.js +7 -3
- package/dist/trace/ledger-sink.d.ts +3 -1
- package/dist/trace/project.d.ts +4 -2
- package/dist/trace/project.js +8 -7
- package/dist/trace/redact.d.ts +12 -0
- package/dist/trace/redact.js +5 -1
- package/dist/trace/self-minted-wire.d.ts +131 -52
- package/dist/trace/self-minted-wire.js +71 -39
- package/dist/web-admin-client/apply-effective.js +22 -9
- package/docs/error-text-lexicon.json +317 -0
- package/package.json +3 -1
- package/scripts/embed-minimal.mjs +183 -0
- package/wire-changes/server@7.149.0.json +372 -0
- package/dist/plain-string-record.js +0 -70
|
@@ -4,13 +4,25 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { atomicWriteFile, ensureDir } from "@sema-agent/core";
|
|
5
5
|
import { mysqlDriver, pgDriver, sqlDbNowMs } from "./sql-driver.js";
|
|
6
6
|
import { retryOnDeadlock } from "./sql-errors.js";
|
|
7
|
-
import { alignedWindow } from "../observability/cost-quota.js";
|
|
7
|
+
import { alignedWindow, retryAfterSecUntil } from "../observability/cost-quota.js";
|
|
8
8
|
export const ORG_DELIVERY_SENDER_QUOTA_TABLE = "org_delivery_sender_quota";
|
|
9
9
|
export const ORG_DELIVERY_RECIPIENT_QUOTA_TABLE = "org_delivery_recipient_quota";
|
|
10
10
|
export const ORG_DELIVERY_PAIR_QUOTA_TABLE = "org_delivery_pair_quota";
|
|
11
|
+
export const ORG_DELIVERY_QUOTA_UNREADABLE_SENTENCE = "the cross-principal delivery quota state on this server cannot be read, so every delivery is refused until an operator repairs it — nothing was delivered, and retrying will not change the answer";
|
|
12
|
+
export class OrgDeliveryQuotaUnreadableError extends Error {
|
|
13
|
+
constructor(operatorSentence) {
|
|
14
|
+
super(operatorSentence);
|
|
15
|
+
this.name = "OrgDeliveryQuotaUnreadableError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
11
18
|
export function quotaWindowStart(nowMs, windowMs) {
|
|
12
19
|
return alignedWindow(nowMs, windowMs).bucket * windowMs;
|
|
13
20
|
}
|
|
21
|
+
const GRANTED = Object.freeze({ granted: true });
|
|
22
|
+
export function quotaRetryAfterSec(nowMs, windowMs) {
|
|
23
|
+
return retryAfterSecUntil(alignedWindow(nowMs, windowMs));
|
|
24
|
+
}
|
|
25
|
+
const refused = (nowMs, windowMs) => ({ granted: false, retryAfterSec: quotaRetryAfterSec(nowMs, windowMs) });
|
|
14
26
|
export function heldWindowCounts(heldStart, at, windowMs) {
|
|
15
27
|
return heldStart === at || heldStart === at + windowMs;
|
|
16
28
|
}
|
|
@@ -21,25 +33,27 @@ export class MemoryOrgDeliveryQuotaStore {
|
|
|
21
33
|
this.nowMs = nowMs;
|
|
22
34
|
}
|
|
23
35
|
async admitAttempt(fromScope, policy) {
|
|
24
|
-
const
|
|
36
|
+
const now = this.nowMs();
|
|
37
|
+
const at = quotaWindowStart(now, policy.windowMs);
|
|
25
38
|
const held = this.state.senders.get(fromScope);
|
|
26
39
|
const current = held !== undefined && heldWindowCounts(held.windowStart, at, policy.windowMs) ? held : { windowStart: at, windowMs: policy.windowMs, attempts: 0 };
|
|
27
40
|
if (current.attempts >= policy.attemptsPerSender)
|
|
28
|
-
return
|
|
41
|
+
return refused(now, policy.windowMs);
|
|
29
42
|
const senders = new Map(this.state.senders).set(fromScope, { ...current, attempts: current.attempts + 1 });
|
|
30
43
|
this.commit({ senders, recipients: this.state.recipients });
|
|
31
|
-
return
|
|
44
|
+
return GRANTED;
|
|
32
45
|
}
|
|
33
46
|
async take(fromScope, toScope, policy) {
|
|
34
|
-
const
|
|
47
|
+
const now = this.nowMs();
|
|
48
|
+
const at = quotaWindowStart(now, policy.windowMs);
|
|
35
49
|
const held = this.state.recipients.get(toScope);
|
|
36
50
|
const current = held !== undefined && heldWindowCounts(held.windowStart, at, policy.windowMs) ? held : { windowStart: at, windowMs: policy.windowMs, total: 0, pairs: new Map() };
|
|
37
51
|
const pair = current.pairs.get(fromScope) ?? 0;
|
|
38
52
|
if (current.total >= policy.perRecipient || pair >= policy.perPair)
|
|
39
|
-
return
|
|
53
|
+
return refused(now, policy.windowMs);
|
|
40
54
|
const next = { ...current, total: current.total + 1, pairs: new Map(current.pairs).set(fromScope, pair + 1) };
|
|
41
55
|
this.commit({ senders: this.state.senders, recipients: new Map(this.state.recipients).set(toScope, next) });
|
|
42
|
-
return
|
|
56
|
+
return GRANTED;
|
|
43
57
|
}
|
|
44
58
|
commit(next) {
|
|
45
59
|
const now = this.nowMs();
|
|
@@ -97,7 +111,7 @@ export class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaStore {
|
|
|
97
111
|
parsed = { ok: false, problem: err instanceof Error ? err.message : String(err) };
|
|
98
112
|
}
|
|
99
113
|
if (!parsed.ok) {
|
|
100
|
-
this.unusable = `the cross-principal delivery quota file ${this.path} cannot be read (${parsed.problem}) — every delivery is refused until an operator moves it away (moving it resets the current window's counts)`;
|
|
114
|
+
this.unusable = `the cross-principal delivery quota file ${this.path} cannot be read (${parsed.problem}) — every delivery is refused until an operator repairs or moves it away and restarts this server — the file is read once at startup (moving it away resets the current window's counts)`;
|
|
101
115
|
(opts?.warn ?? createLogger().warn)("org_delivery_quota_corrupt_read", { path: this.path, problem: parsed.problem });
|
|
102
116
|
return;
|
|
103
117
|
}
|
|
@@ -105,12 +119,12 @@ export class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaStore {
|
|
|
105
119
|
}
|
|
106
120
|
async admitAttempt(fromScope, policy) {
|
|
107
121
|
if (this.unusable !== undefined)
|
|
108
|
-
throw new
|
|
122
|
+
throw new OrgDeliveryQuotaUnreadableError(this.unusable);
|
|
109
123
|
return super.admitAttempt(fromScope, policy);
|
|
110
124
|
}
|
|
111
125
|
async take(fromScope, toScope, policy) {
|
|
112
126
|
if (this.unusable !== undefined)
|
|
113
|
-
throw new
|
|
127
|
+
throw new OrgDeliveryQuotaUnreadableError(this.unusable);
|
|
114
128
|
return super.take(fromScope, toScope, policy);
|
|
115
129
|
}
|
|
116
130
|
persist(next) {
|
|
@@ -141,16 +155,17 @@ export class SqlOrgDeliveryQuotaStore {
|
|
|
141
155
|
return retryOnDeadlock(this.db.dialect, () => this.inTxOnce(table, policy, body));
|
|
142
156
|
}
|
|
143
157
|
async inTxOnce(table, policy, body) {
|
|
144
|
-
const
|
|
158
|
+
const now = await sqlDbNowMs(this.db, table);
|
|
159
|
+
const at = quotaWindowStart(now, policy.windowMs);
|
|
145
160
|
const conn = await this.db.connect();
|
|
146
161
|
try {
|
|
147
162
|
await conn.begin();
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
163
|
+
const ok = await body(conn, at);
|
|
164
|
+
if (ok)
|
|
150
165
|
await conn.commit();
|
|
151
166
|
else
|
|
152
167
|
await conn.rollback();
|
|
153
|
-
return
|
|
168
|
+
return ok ? GRANTED : refused(now, policy.windowMs);
|
|
154
169
|
}
|
|
155
170
|
catch (err) {
|
|
156
171
|
await conn.rollback().catch((rollbackErr) => {
|
|
@@ -121,7 +121,7 @@ export declare function assertPermissionRuleApprovalSchema(query: (sql: string)
|
|
|
121
121
|
*
|
|
122
122
|
* 判据同款:发一条恒空的 `WHERE 1=0` 读;**只有可证的缺列**才给「缺列」的拒因(其余错误原样 rethrow ——
|
|
123
123
|
* 超时/连接重置/权限/表不在都会让这条读抛,把它们诊断成「缺列」是一条比缺陷更贵的诊断)。出路是**闭集三条**
|
|
124
|
-
* (与整库对账同一份,{@link
|
|
124
|
+
* (与整库对账同一份,{@link probedMissingColumnRefusal}),没有销毁性的一条。
|
|
125
125
|
*
|
|
126
126
|
* 文案照实说本表的分量:票是分钟级 TTL 的治理瞬态(过期即死),持久规则在 `permission_rule` 里,一行都不动。
|
|
127
127
|
*/
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, parseRuleText, readRowBehavior, screenRuleSyncState, PERMISSION_RULE_WRITER, RULE_ADD_ORIGINS, RULE_APPROVAL_KINDS, RULE_APPROVAL_RECORD_SCHEMA, RULE_APPROVAL_STATES, RULE_BEHAVIORS, RULE_SYNC_DROP_CODES, EDITED_RULE_BREADTH_WARNING_CODES, PERSISTED_RULE_MATCHES, EMPTY_DURABLE_RULE_PARTITION, PERMISSION_RULES_LATER_GRAMMAR_CODE, SESSION_RULES_GRAMMAR, readRuleGrammarStamp, readRuleOwner, principalNameOf, readRuleOffer2, } from "@sema-agent/core";
|
|
4
4
|
import { dialectProtocolJsonEncoder, sqlDbNowMs, SQL_DB_NOW_MS } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
|
-
import {
|
|
6
|
+
import { probedMissingColumnRefusal } from "./schema-drift-sentence.js";
|
|
7
7
|
import { canonicalJsonStringify } from "@sema-agent/core";
|
|
8
8
|
import { ensureIndexes } from "./ensure-index.js";
|
|
9
9
|
import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, StoreCorruptError } from "./sql-json-column.js";
|
|
@@ -234,15 +234,13 @@ export async function assertPermissionRuleApprovalSchema(query, dialect) {
|
|
|
234
234
|
catch (err) {
|
|
235
235
|
if (!isMissingColumnError(err, dialect))
|
|
236
236
|
throw err;
|
|
237
|
-
throw
|
|
237
|
+
throw await probedMissingColumnRefusal(query, dialect, [PERMISSION_RULE_APPROVAL_TABLE], `${PERMISSION_RULE_APPROVAL_TABLE} is missing required columns — refusing to start. This build needs the ` +
|
|
238
238
|
`#340 card-edit columns (command / edited_json) and the design/375 record-form columns ` +
|
|
239
239
|
`(version / offers_json / selected_offer): the approval record now stores its OPTION structure and the ` +
|
|
240
240
|
`chosen offer, and the store refuses to read rows whose record-form version it cannot vouch for. ` +
|
|
241
241
|
`This table holds pending/settled approval RECORDS (the audit trail of who said yes to which ` +
|
|
242
242
|
`rule), not the rules themselves — persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched; ` +
|
|
243
|
-
`an in-place ALTER keeps the record trail
|
|
244
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
245
|
-
(await describeProbedTableDrift(query, dialect, [PERMISSION_RULE_APPROVAL_TABLE])));
|
|
243
|
+
`an in-place ALTER keeps the record trail.`, err);
|
|
246
244
|
}
|
|
247
245
|
}
|
|
248
246
|
export async function assertPermissionRuleTicketSchema(query, dialect) {
|
|
@@ -252,14 +250,12 @@ export async function assertPermissionRuleTicketSchema(query, dialect) {
|
|
|
252
250
|
catch (err) {
|
|
253
251
|
if (!isMissingColumnError(err, dialect))
|
|
254
252
|
throw err;
|
|
255
|
-
throw
|
|
253
|
+
throw await probedMissingColumnRefusal(query, dialect, [PERMISSION_RULE_TICKET_TABLE], `${PERMISSION_RULE_TICKET_TABLE} is missing required columns (redeemed_outcome / claim_id / purpose / settled_at_ms) — refusing to start. This build needs ` +
|
|
256
254
|
`the #265 A7 three-state redemption columns (MINTED → CLAIMED → SETTLED, the claim generation that fences a superseded ` +
|
|
257
255
|
`claimant, and the lane binding that keeps cc-import and local-import tickets from being interchangeable): without them a ` +
|
|
258
256
|
`local-import ticket can be claimed but never settled, so a redemption that already wrote rules to the store answers its ` +
|
|
259
257
|
`owner with a retry-later it can never satisfy. This table holds minute-TTL single-use import ` +
|
|
260
|
-
`tickets (a governance transient that dies on expiry); persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched
|
|
261
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
262
|
-
(await describeProbedTableDrift(query, dialect, [PERMISSION_RULE_TICKET_TABLE])));
|
|
258
|
+
`tickets (a governance transient that dies on expiry); persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched.`, err);
|
|
263
259
|
}
|
|
264
260
|
}
|
|
265
261
|
export const PERMISSION_RULE_INDEXES = [
|
|
@@ -51,7 +51,7 @@ export declare function createPgPool(opts: string | pg.PoolConfig, onIdleClientE
|
|
|
51
51
|
export declare const ENSURE_SCHEMA_LOCK_KEY = 4906;
|
|
52
52
|
/**
|
|
53
53
|
* {@link ENSURE_SCHEMA_LOCK_KEY} 的**非阻塞**获取(轮询 + 抖动退避)—— MySQL 孪生
|
|
54
|
-
* `tidb-pool.ts acquireEnsureSchemaLock` 的逐条同形(
|
|
54
|
+
* `tidb-pool.ts acquireEnsureSchemaLock` 的逐条同形(两只都是 `ensure-schema-lock-policy.ts` 取锁器的薄壳:语句 / 读法 / 循环共用)。轮到拿到为止;坏连接连续抛上抛
|
|
55
55
|
* (每条 DDL 本来就各自幂等),而不是把启动挂死。
|
|
56
56
|
*
|
|
57
57
|
* 🔴 **为什么不能再用阻塞的 `pg_advisory_lock()`**([ref] 复审 H1,对 7.82.1 的回归):本仓自 [ref] 起在
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createLogger
|
|
1
|
+
import { createLogger } from "../observability/logger.js";
|
|
2
2
|
import pg from "pg";
|
|
3
3
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
4
|
-
import {
|
|
4
|
+
import { acquireAdvisoryLock, releaseAdvisoryLock } from "./ensure-schema-lock-policy.js";
|
|
5
5
|
import { RUN_IDENTITY_COLUMNS } from "../identity-source.js";
|
|
6
6
|
import { ensureSchema as ensureSessionSchema } from "../stores-sql/pg-session-storage.js";
|
|
7
7
|
import { sessionExtDdl } from "./session-ext-table.js";
|
|
@@ -26,6 +26,7 @@ import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
|
|
|
26
26
|
import { INBOX_PAYLOAD_COLUMN } from "./inbox-payload-column.js";
|
|
27
27
|
import { CENTRAL_SCHEMA_INDEXES } from "./schema-indexes.js";
|
|
28
28
|
import { PRINCIPAL_MAX_BYTES } from "../principal-shape.js";
|
|
29
|
+
import { SKILL_EXEMPTION_CUTOFF_ID, markIfAbsentSql, migrationMarkerDdl } from "./one-time-migration-marker.js";
|
|
29
30
|
export const PG_SCHEMA_STATEMENTS = [
|
|
30
31
|
`CREATE TABLE IF NOT EXISTS task_run (
|
|
31
32
|
task_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -36,6 +37,8 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
36
37
|
error TEXT COLLATE "C",
|
|
37
38
|
error_code VARCHAR(64) COLLATE "C",
|
|
38
39
|
instance_id VARCHAR(64) COLLATE "C",
|
|
40
|
+
-- leg_id: the HOLDING LEG minted at every claim (semantics in the MySQL twin, tidb-pool.ts task_run).
|
|
41
|
+
leg_id VARCHAR(36) COLLATE "C",
|
|
39
42
|
job_id VARCHAR(64) COLLATE "C",
|
|
40
43
|
source VARCHAR(64) COLLATE "C",
|
|
41
44
|
-- identity_*: the run's identity tuple (semantics in the MySQL twin, tidb-pool.ts task_run; column definitions' single owner =
|
|
@@ -417,6 +420,7 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
417
420
|
updated_at_ms BIGINT NOT NULL,
|
|
418
421
|
PRIMARY KEY (store_id, path)
|
|
419
422
|
)`,
|
|
423
|
+
migrationMarkerDdl("pg"),
|
|
420
424
|
];
|
|
421
425
|
export function pgPoolOptions(pgCfg, dbQueryTimeoutMs) {
|
|
422
426
|
return {
|
|
@@ -475,19 +479,12 @@ export function createPgPool(opts, onIdleClientError) {
|
|
|
475
479
|
return pool;
|
|
476
480
|
}
|
|
477
481
|
export const ENSURE_SCHEMA_LOCK_KEY = 4906;
|
|
482
|
+
const ENSURE_SCHEMA_LOCK_ID = { dialect: "pg", keys: [ENSURE_SCHEMA_LOCK_KEY] };
|
|
478
483
|
export async function acquirePgEnsureSchemaLock(client, tag) {
|
|
479
|
-
await
|
|
480
|
-
const res = await client.query("SELECT pg_try_advisory_lock($1) AS got", [ENSURE_SCHEMA_LOCK_KEY]);
|
|
481
|
-
return res.rows[0]?.got === true;
|
|
482
|
-
}, { tag, label: `pg advisory lock ${ENSURE_SCHEMA_LOCK_KEY}` });
|
|
484
|
+
await acquireAdvisoryLock(client, ENSURE_SCHEMA_LOCK_ID, tag);
|
|
483
485
|
}
|
|
484
486
|
export async function releasePgEnsureSchemaLock(client, tag) {
|
|
485
|
-
|
|
486
|
-
await client.query("SELECT pg_advisory_unlock($1)", [ENSURE_SCHEMA_LOCK_KEY]);
|
|
487
|
-
}
|
|
488
|
-
catch (err) {
|
|
489
|
-
writeLogText("stderr", `[${tag}] pg_advisory_unlock(${ENSURE_SCHEMA_LOCK_KEY}) failed (lock auto-frees on connection close):`, err);
|
|
490
|
-
}
|
|
487
|
+
await releaseAdvisoryLock(client, ENSURE_SCHEMA_LOCK_ID, tag);
|
|
491
488
|
}
|
|
492
489
|
export async function ensurePgSchema(pool) {
|
|
493
490
|
const client = await pool.connect();
|
|
@@ -497,6 +494,7 @@ export async function ensurePgSchema(pool) {
|
|
|
497
494
|
locked = true;
|
|
498
495
|
for (const stmt of PG_SCHEMA_STATEMENTS)
|
|
499
496
|
await client.query(stmt);
|
|
497
|
+
await client.query(markIfAbsentSql("pg"), [SKILL_EXEMPTION_CUTOFF_ID, Date.now()]);
|
|
500
498
|
await ensureSessionSchema(client);
|
|
501
499
|
await client.query(sessionExtDdl("pg"));
|
|
502
500
|
await ensureToolResultSchema(client);
|
|
@@ -331,14 +331,15 @@ export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): E
|
|
|
331
331
|
/**
|
|
332
332
|
* §4.3.3 的 cwd 优先序 —— 与 host 腿的 `effectiveHostWorkspace` **同源**:
|
|
333
333
|
* ① 本 session 显式注册的 cwd(部署形表行 `callerCwd` 闸下由 resolve-spec 写);
|
|
334
|
-
* ② `ctx.parentCwd`(
|
|
334
|
+
* ② `ctx.parentCwd`(交活方的工作目录:委派子代继承父的;组合腿 —— 梯子的级、验证器腿,跑在引擎现铸的会话里 —— 由组合入口交提交登记的 cwd;
|
|
335
|
+
* 少了它这些腿会丢工作目录);
|
|
335
336
|
* ③ 设备入籍时登记并落库的 `workspaceRoot`(缺 cwd 时的解析基准,有明确 wire 契约,不是「executor
|
|
336
337
|
* 默认根」这种空话)。
|
|
337
338
|
*
|
|
338
339
|
* 🔴 **这一只解析源同时守它的后置条件:结果必是 POSIX 绝对路径,否则响亮抛 typed 拒(`.code` = `config.device_cwd_invalid`)。** 协议文档对 executor 的承诺是
|
|
339
340
|
* 「exec / execStream 的指令帧恒带绝对 `cwd`」—— 不带或带相对的,都会把「用哪个目录」这件事留给 executor
|
|
340
341
|
* 自己猜(同一个事实两种读法)。判据是入籍口那**同一只**谓词 `isDeviceAbsolutePath`(`device-store.ts`):入籍口用它拒相对的
|
|
341
|
-
* `workspaceRoot`;这里的判留作存量行守卫(入籍口收紧之前落库的相对根)并守另两个来源 ——
|
|
342
|
+
* `workspaceRoot`;这里的判留作存量行守卫(入籍口收紧之前落库的相对根)并守另两个来源 —— 第 ② 臂的交活方工作目录不经入籍口。
|
|
342
343
|
*/
|
|
343
344
|
export declare function effectiveDeviceCwd(sessionCwd: string | undefined, parentCwd: string | undefined, row: Pick<DeviceRow, "workspaceRoot">): string;
|
|
344
345
|
export {};
|
|
@@ -244,13 +244,7 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
244
244
|
private execInternal;
|
|
245
245
|
private execAs;
|
|
246
246
|
execStream(command: string, options?: ExecStreamOptions): AsyncIterable<OutputChunk>;
|
|
247
|
-
readonly backgroundCapabilities:
|
|
248
|
-
readonly supported: true;
|
|
249
|
-
readonly maxConcurrent: number;
|
|
250
|
-
readonly defaultBgTimeoutSec: number;
|
|
251
|
-
readonly maxBgTimeoutSec: number;
|
|
252
|
-
readonly supportsDetach: true;
|
|
253
|
-
};
|
|
247
|
+
readonly backgroundCapabilities: BackgroundShellCapability["backgroundCapabilities"];
|
|
254
248
|
private _bgManager?;
|
|
255
249
|
private get bgManager();
|
|
256
250
|
private makeBgDriver;
|
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { Sandbox, CommandExitError, FileType } from "e2b";
|
|
3
3
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
4
4
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
5
|
+
import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
|
|
5
6
|
import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, e2bFloorCommand, engineStart, } from "./sandbox-read-floor.js";
|
|
6
7
|
import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
|
|
7
8
|
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
@@ -55,6 +56,14 @@ export class RemoteContainerExecutionEnv {
|
|
|
55
56
|
this.sandboxApi = deps.sandboxApi ?? Sandbox;
|
|
56
57
|
this.floor = deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor, ...(config.logger !== undefined ? { logger: config.logger } : {}) });
|
|
57
58
|
this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER }, sandboxReadFloor: config.sandboxReadFloor });
|
|
59
|
+
const backgroundShellOn = DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds({ remoteExec: { provider: PROVIDER } });
|
|
60
|
+
this.backgroundCapabilities = {
|
|
61
|
+
supported: backgroundShellOn,
|
|
62
|
+
maxConcurrent: E2B_BG_MAX_CONCURRENT,
|
|
63
|
+
defaultBgTimeoutSec: E2B_BG_DEFAULT_TIMEOUT_SEC,
|
|
64
|
+
maxBgTimeoutSec: E2B_BG_MAX_TIMEOUT_SEC,
|
|
65
|
+
supportsDetach: backgroundShellOn,
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
capabilities;
|
|
60
69
|
floor;
|
|
@@ -764,13 +773,7 @@ export class RemoteContainerExecutionEnv {
|
|
|
764
773
|
options?.signal?.removeEventListener("abort", onAbort);
|
|
765
774
|
}
|
|
766
775
|
}
|
|
767
|
-
backgroundCapabilities
|
|
768
|
-
supported: true,
|
|
769
|
-
maxConcurrent: E2B_BG_MAX_CONCURRENT,
|
|
770
|
-
defaultBgTimeoutSec: E2B_BG_DEFAULT_TIMEOUT_SEC,
|
|
771
|
-
maxBgTimeoutSec: E2B_BG_MAX_TIMEOUT_SEC,
|
|
772
|
-
supportsDetach: true,
|
|
773
|
-
};
|
|
776
|
+
backgroundCapabilities;
|
|
774
777
|
_bgManager;
|
|
775
778
|
get bgManager() {
|
|
776
779
|
return (this._bgManager ??= new BackgroundShellManager(this.makeBgDriver(), this.backgroundCapabilities));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type ExecResult, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions, type SecretEnvFinding } from "@sema-agent/core";
|
|
1
|
+
import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type ExecResult, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type ExecutionEnvFactoryContext, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions, type SecretEnvFinding } from "@sema-agent/core";
|
|
2
2
|
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
3
|
+
import { type CustodyMember } from "./workspace-custody.js";
|
|
3
4
|
/** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
|
|
4
5
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
5
6
|
/** SIGTERM → grace → SIGKILL (no host isolation). 🔴 宽限的**唯一属主**:本 driver 的 `kill` 梯子用它排升级定时器;会话级收敛
|
|
@@ -10,7 +11,7 @@ export declare const HOST_BG_KILL_GRACE_MS: number;
|
|
|
10
11
|
export interface HostEnvConfig extends LaneHomeConfig {
|
|
11
12
|
/**
|
|
12
13
|
* Base directory under which this adapter creates its per-task working directory. Default {@link os.tmpdir}.
|
|
13
|
-
* The actual workspace is `<workspaceBase>/sema-host-<id
|
|
14
|
+
* The actual workspace is `<workspaceBase>/sema-host-<id>-<rand>`; its last holder `rm -rf`s it on {@link destroy} (see {@link custody}).
|
|
14
15
|
*/
|
|
15
16
|
workspaceBase?: string;
|
|
16
17
|
/**
|
|
@@ -44,14 +45,21 @@ export interface HostEnvConfig extends LaneHomeConfig {
|
|
|
44
45
|
* user's OWN directory (e.g. the cwd `run-local` was invoked in), not an adapter-created ephemeral sandbox,
|
|
45
46
|
* so the agent's file edits PERSIST and are user-visible (like a local coding agent working in your repo).
|
|
46
47
|
* Takes precedence over `workspaceBase`/`id` (those are ignored). Default unset = ephemeral random + cleanup.
|
|
48
|
+
* 唯一的例外由托管表说:它落在一只本服务铸的临时根里({@link custody} 的 `sharedRoot`)⇒ 本环境共持那只根,不是调用方自有目录。
|
|
47
49
|
*/
|
|
48
50
|
workspaceDir?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 工厂的工作区托管表交给它铸的每一只环境的那一面({@link hostExecutionEnvFactory} 装配;嵌入方直接构造的环境不带 ⇒ 不在任何表里,没有别的持有者)。
|
|
53
|
+
*/
|
|
54
|
+
custody?: HostWorkspaceCustodySeat;
|
|
49
55
|
/** R7 self-wake: the injected `SchedulerCapability` backend (the TOC self-wake scheduler write side).
|
|
50
56
|
* When set, `hasScheduler(env)` is true and core mounts CronCreate/CronDelete/CronList(旧名 CronCancel;Sleep 已 [ref] 撤除;挂载在 prepare-task 非 createHandsToolkit——L8 订正); when absent the env
|
|
51
57
|
* reports `supported:false` → INERT (no scheduler tools). Injected at boot only on the single-user host lane. */
|
|
52
58
|
scheduler?: SchedulerCapability;
|
|
53
|
-
/** [ref] background shell:
|
|
54
|
-
*
|
|
59
|
+
/** [ref] background shell: the assembly hands the deployment-shape row's answer here
|
|
60
|
+
* (`DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds(config)`); `false` → INERT (multi-tenant host lane gate).
|
|
61
|
+
* Default (undefined) = the SAME row's answer for an undeclared principal (the single-user TOC host lane), never a literal.
|
|
62
|
+
* `HOST_BG_ENABLED=false` is an additional global kill switch(🪦 旧名
|
|
55
63
|
* `HOST_BG_DISABLED` 自 server 3.0.0 起是 fail-loud 墓碑:设了即拒启并指路新名,不再被读取)。 */
|
|
56
64
|
backgroundShell?: boolean;
|
|
57
65
|
/**
|
|
@@ -61,7 +69,14 @@ export interface HostEnvConfig extends LaneHomeConfig {
|
|
|
61
69
|
*/
|
|
62
70
|
gitBase?: true;
|
|
63
71
|
}
|
|
64
|
-
|
|
72
|
+
/** 工作区托管表交给它铸的环境的那一面(`workspace-custody.ts`;host 车道的读法)。 */
|
|
73
|
+
export interface HostWorkspaceCustodySeat {
|
|
74
|
+
/** `workspaceDir` 落在这只本服务铸的临时根里(委派子代按父的工作目录落根)⇒ 本环境与它的铸者共持这只根:建成后登记、收场时放手、最后放手的持有者删它。 */
|
|
75
|
+
readonly sharedRoot?: string;
|
|
76
|
+
/** 表里除 `self` 之外还有没有成员持有 `root` —— 收场放手时问;有 ⇒ 删交给最后放手的那一只。 */
|
|
77
|
+
heldElsewhere(self: RemoteHostExecutionEnv, root: string): boolean;
|
|
78
|
+
}
|
|
79
|
+
export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability, CustodyMember<never> {
|
|
65
80
|
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
66
81
|
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
67
82
|
/** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
|
|
@@ -106,6 +121,9 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
106
121
|
private prepared;
|
|
107
122
|
/** Terminal once {@link destroy} ran — guards fs/exec from running against a removed workspace. */
|
|
108
123
|
private destroyed;
|
|
124
|
+
/** {@link destroy} 的那一次收场(第二次调用等同一只);托管表读它({@link releasing}):收场在飞时同一身份不铸新的。 */
|
|
125
|
+
private teardown?;
|
|
126
|
+
private teardownSettled;
|
|
109
127
|
/** [ref] 五轮复审:fg/bg spool 根=tmpdir 私有目录(core bgSpoolDir 同形)。workspace 是命令可写区,
|
|
110
128
|
* `git clean -fdx` 类清理会 unlink 活跃 spool(子进程经继承 fd 对 unlinked inode 继续写,而父进程
|
|
111
129
|
* 按路径 statSync/read 静默断=输出丢失+轮转帽失效);顺带不再弄脏 user-owned workspace(TOC/CC
|
|
@@ -172,12 +190,22 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
172
190
|
*/
|
|
173
191
|
private drainInflightFs;
|
|
174
192
|
private ensureSpoolRoot;
|
|
175
|
-
/**
|
|
176
|
-
private
|
|
193
|
+
/** 本环境对它工作区的租期({@link HostWorkspaceTenure}):借用的永不删;持有的收场放手、最后放手的持有者删。唯一写处 = 构造与 {@link removeOwnedTrees}。 */
|
|
194
|
+
private tenure;
|
|
177
195
|
/** Memoized lazy `mkdir -p` so concurrent first-use prepares the workspace exactly once. */
|
|
178
196
|
private preparing?;
|
|
179
197
|
private readonly handle;
|
|
180
198
|
constructor(config?: HostEnvConfig);
|
|
199
|
+
/** 收场的进度(托管表读):活着 ⇒ 缺席;{@link destroy} 开始 ⇒ 那一次收场。 */
|
|
200
|
+
get releasing(): Promise<void> | undefined;
|
|
201
|
+
/** 收场已落定。 */
|
|
202
|
+
get released(): boolean;
|
|
203
|
+
/** 本车道收场不欠同一身份下一只环境任何账(删不掉的根留在退出兜底簿上,不交接)。 */
|
|
204
|
+
readonly leftover: undefined;
|
|
205
|
+
/** 收场落定且已放手 ⇒ 出托管表(超时臂里放手挂在在飞写之后:落定了还持有 ⇒ 仍是持有者)。 */
|
|
206
|
+
get settled(): boolean;
|
|
207
|
+
/** 本环境此刻持有的、本服务铸的临时根(托管表的持有读法读它);借用 / 已放手 ⇒ 缺席。 */
|
|
208
|
+
get heldWorkspace(): string | undefined;
|
|
181
209
|
workspaceHandle(): WorkspaceHandle;
|
|
182
210
|
connect(config?: RemoteConnectConfig): Promise<{
|
|
183
211
|
ok: true;
|
|
@@ -223,6 +251,7 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
223
251
|
* core 留活过的驻留 watcher 又杀了一遍(run 尾 dispose→destroy 顺序,destroy 是第二刀)。隔离 env(容器/VM)
|
|
224
252
|
* 整体拆机自然带走进程,与本契约不冲突(k8s/e2b 各自 destroy 不走这条路径)。 */
|
|
225
253
|
destroy(): Promise<void>;
|
|
254
|
+
private tearDown;
|
|
226
255
|
/** `destroy()` 真正动手删的那一段(本 env **自有**的两棵树:spool 根 + ephemeral workspace)。
|
|
227
256
|
* 抽成一只是因为它有两个调用时机 —— 在飞写按时清空时由 `destroy()` 直接 await,超时时挂在那批写后面异步跑
|
|
228
257
|
* (见 {@link drainInflightFs} 头注③)。永不抛(teardown 契约)。 */
|
|
@@ -373,7 +402,7 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
373
402
|
* 查不到根因;更糟的是每一个任务都复发一次同一条。⇒ 前判把它换成一句能直接照做的拒绝:哪个目录、
|
|
374
403
|
* 哪个用户、改哪个旋钮。
|
|
375
404
|
*
|
|
376
|
-
*
|
|
405
|
+
* 只对**自铸**的那一只做(没给 `workspaceDir`;给了的是用户自己的目录或父那只已判过的根,`mkdir -p` 一个已存在的只读 checkout 本来
|
|
377
406
|
* 就该成功 —— 在那儿加可写判据会把"只读仓库上跑只读任务"这个正当用法拒掉)。
|
|
378
407
|
* root 与非 POSIX 平台跳过:root 的可写判据恒真(root-squash 的网络盘上反而会假拒),
|
|
379
408
|
* 而 `process.getuid` 不存在时这条 POSIX 权限判据无从谈起。
|
|
@@ -453,6 +482,18 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
453
482
|
* Mirrors `e2bExecutionEnvFactory`: returns the env **unconnected** (lazy) and folds the per-task
|
|
454
483
|
* `ctx.sessionId` into the workspace dir id so two concurrent tasks never share a working directory.
|
|
455
484
|
*/
|
|
456
|
-
|
|
485
|
+
/** 一条腿的环境配置里随腿变的那几格({@link hostExecutionEnvFactory} 第二参:工厂上下文 → 值)。 */
|
|
486
|
+
export interface HostLegConfig extends Pick<HostEnvConfig, "workspaceDir" | "env" | "inheritEnv"> {
|
|
487
|
+
/** 铸成那一拍对这只环境做的一次装饰(交回已有环境时不再做)。 */
|
|
488
|
+
readonly minted?: (env: RemoteHostExecutionEnv) => void;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* host 车道的工厂。**一只身份一只工作区**:经工作区托管表(`workspace-custody.ts`,与 local-docker 车道同一只)铸环境 ——
|
|
492
|
+
* 同一身份(`ctx.taskId ?? ctx.sessionId`)还有一只没收场的环境 ⇒ 交回它(停驻腿的环境 = 续跑腿的环境:引擎 park-only 契约要的「重连到同一工作区」);
|
|
493
|
+
* 正在收场 ⇒ 等它落定再铸。铸的时候问同一张表「腿的目录落在哪只本服务铸的临时根里」:落在里面(委派子代按父的工作目录落根)⇒ 与铸者共持那只根,
|
|
494
|
+
* 父收场时子代还持有它就不删,最后放手的持有者删。`legOf` 先于交回 / 铸判(它可以拒)。
|
|
495
|
+
* 起服铸**一只**工厂(每次调用另铸一只工厂 = 没有表)。
|
|
496
|
+
*/
|
|
497
|
+
export declare function hostExecutionEnvFactory(config?: HostEnvConfig, legOf?: (ctx: ExecutionEnvFactoryContext) => HostLegConfig): ExecutionEnvFactory;
|
|
457
498
|
export { localDockerExecutionEnvFactory, type LocalDockerEnvConfig, type LocalDockerEnvDeps, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
|
|
458
499
|
//# sourceMappingURL=remote-env-host.d.ts.map
|
|
@@ -14,10 +14,12 @@ import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions,
|
|
|
14
14
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
15
15
|
import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
16
16
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
17
|
+
import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
|
|
17
18
|
import { LaneHomeSeat, laneHomeRefusal } from "../lane-home-probe.js";
|
|
18
19
|
import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
|
|
19
20
|
import { nameTooLongCode } from "./remote-env-file-error.js";
|
|
20
21
|
import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
|
|
22
|
+
import { createWorkspaceCustody } from "./workspace-custody.js";
|
|
21
23
|
import { OWNER_ONLY_FILE_MODE } from "./owner-only-file-mode.js";
|
|
22
24
|
const PROVIDER = "host";
|
|
23
25
|
const DEFAULT_COMMAND_TIMEOUT_MS = 30 * 60_000;
|
|
@@ -34,6 +36,8 @@ const unsupported = (op) => ({
|
|
|
34
36
|
ok: false,
|
|
35
37
|
error: new RemoteExecutionError("unsupported", `${op} is not supported on a host target (the operator's own machine — no container boundary, not snapshotable; capabilities.suspendable=false)`),
|
|
36
38
|
});
|
|
39
|
+
const BORROWED = Object.freeze({ kind: "borrowed" });
|
|
40
|
+
const RELEASED = Object.freeze({ kind: "released" });
|
|
37
41
|
const UNSUPPORTED_SCHEDULER_CAPS = {
|
|
38
42
|
supported: false,
|
|
39
43
|
maxScheduledPerScope: 0,
|
|
@@ -80,6 +84,8 @@ export class RemoteHostExecutionEnv {
|
|
|
80
84
|
cfg;
|
|
81
85
|
prepared = false;
|
|
82
86
|
destroyed = false;
|
|
87
|
+
teardown;
|
|
88
|
+
teardownSettled = false;
|
|
83
89
|
spoolRootDir;
|
|
84
90
|
liveFgSpools = new Set();
|
|
85
91
|
inflightFs = new Set();
|
|
@@ -122,20 +128,21 @@ export class RemoteHostExecutionEnv {
|
|
|
122
128
|
this.spoolRootDir = mkdtempSync(path.join(os.tmpdir(), "sema-host-spool-"));
|
|
123
129
|
return this.spoolRootDir;
|
|
124
130
|
}
|
|
125
|
-
|
|
131
|
+
tenure;
|
|
126
132
|
preparing;
|
|
127
133
|
handle;
|
|
128
134
|
constructor(config = {}) {
|
|
129
135
|
if (config.workspaceDir) {
|
|
130
136
|
this.workspaceDir = path.resolve(config.workspaceDir);
|
|
131
|
-
|
|
137
|
+
const shared = config.custody?.sharedRoot;
|
|
138
|
+
this.tenure = shared !== undefined ? { kind: "held", root: path.resolve(shared) } : BORROWED;
|
|
132
139
|
}
|
|
133
140
|
else {
|
|
134
141
|
const base = config.workspaceBase ?? os.tmpdir();
|
|
135
142
|
const id = config.id ?? randomBytes(6).toString("hex");
|
|
136
143
|
const suffix = randomBytes(4).toString("hex");
|
|
137
144
|
this.workspaceDir = path.resolve(base, `sema-host-${sanitizeId(id)}-${suffix}`);
|
|
138
|
-
this.
|
|
145
|
+
this.tenure = { kind: "held", root: this.workspaceDir };
|
|
139
146
|
}
|
|
140
147
|
const inheritEnvProblem = inheritEnvPolicyProblem(config.inheritEnv);
|
|
141
148
|
if (inheritEnvProblem !== undefined)
|
|
@@ -156,6 +163,19 @@ export class RemoteHostExecutionEnv {
|
|
|
156
163
|
if (homeVerdict !== undefined)
|
|
157
164
|
throw laneHomeRefusal(homeVerdict);
|
|
158
165
|
}
|
|
166
|
+
get releasing() {
|
|
167
|
+
return this.teardown;
|
|
168
|
+
}
|
|
169
|
+
get released() {
|
|
170
|
+
return this.teardownSettled;
|
|
171
|
+
}
|
|
172
|
+
leftover = undefined;
|
|
173
|
+
get settled() {
|
|
174
|
+
return this.teardownSettled && this.tenure.kind !== "held";
|
|
175
|
+
}
|
|
176
|
+
get heldWorkspace() {
|
|
177
|
+
return this.tenure.kind === "held" ? this.tenure.root : undefined;
|
|
178
|
+
}
|
|
159
179
|
workspaceHandle() {
|
|
160
180
|
return this.handle;
|
|
161
181
|
}
|
|
@@ -190,9 +210,13 @@ export class RemoteHostExecutionEnv {
|
|
|
190
210
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
191
211
|
return ok(this.handle);
|
|
192
212
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
213
|
+
destroy() {
|
|
214
|
+
this.teardown ??= this.tearDown().finally(() => {
|
|
215
|
+
this.teardownSettled = true;
|
|
216
|
+
});
|
|
217
|
+
return this.teardown;
|
|
218
|
+
}
|
|
219
|
+
async tearDown() {
|
|
196
220
|
this.destroyed = true;
|
|
197
221
|
const p = this.preparing;
|
|
198
222
|
this.preparing = undefined;
|
|
@@ -215,11 +239,15 @@ export class RemoteHostExecutionEnv {
|
|
|
215
239
|
catch { }
|
|
216
240
|
this.spoolRootDir = undefined;
|
|
217
241
|
}
|
|
218
|
-
|
|
242
|
+
const tenure = this.tenure;
|
|
243
|
+
if (tenure.kind !== "held")
|
|
244
|
+
return;
|
|
245
|
+
this.tenure = RELEASED;
|
|
246
|
+
if (this.cfg.custody?.heldElsewhere(this, tenure.root) === true)
|
|
219
247
|
return;
|
|
220
248
|
try {
|
|
221
|
-
await fs.rm(
|
|
222
|
-
untrackEphemeralHostWorkspace(
|
|
249
|
+
await fs.rm(tenure.root, { recursive: true, force: true });
|
|
250
|
+
untrackEphemeralHostWorkspace(tenure.root);
|
|
223
251
|
}
|
|
224
252
|
catch {
|
|
225
253
|
}
|
|
@@ -228,12 +256,13 @@ export class RemoteHostExecutionEnv {
|
|
|
228
256
|
return this.destroy();
|
|
229
257
|
}
|
|
230
258
|
get backgroundCapabilities() {
|
|
259
|
+
const backgroundShellOn = (this.cfg.backgroundShell ?? DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds({ remoteExec: { provider: PROVIDER } })) && hostBackgroundShellEnabled();
|
|
231
260
|
return {
|
|
232
|
-
supported:
|
|
261
|
+
supported: backgroundShellOn,
|
|
233
262
|
maxConcurrent: HOST_BG_MAX_CONCURRENT,
|
|
234
263
|
defaultBgTimeoutSec: HOST_BG_DEFAULT_TIMEOUT_SEC,
|
|
235
264
|
maxBgTimeoutSec: HOST_BG_MAX_TIMEOUT_SEC,
|
|
236
|
-
supportsDetach:
|
|
265
|
+
supportsDetach: backgroundShellOn,
|
|
237
266
|
};
|
|
238
267
|
}
|
|
239
268
|
_bgManager;
|
|
@@ -1251,7 +1280,7 @@ export class RemoteHostExecutionEnv {
|
|
|
1251
1280
|
ensurePrepared() {
|
|
1252
1281
|
if (this.prepared)
|
|
1253
1282
|
return Promise.resolve();
|
|
1254
|
-
if (
|
|
1283
|
+
if (this.cfg.workspaceDir === undefined) {
|
|
1255
1284
|
try {
|
|
1256
1285
|
this.assertWorkspaceParentWritable();
|
|
1257
1286
|
}
|
|
@@ -1264,8 +1293,8 @@ export class RemoteHostExecutionEnv {
|
|
|
1264
1293
|
.mkdir(this.workspaceDir, { recursive: true })
|
|
1265
1294
|
.then(() => {
|
|
1266
1295
|
this.prepared = true;
|
|
1267
|
-
if (
|
|
1268
|
-
trackEphemeralHostWorkspace(this.
|
|
1296
|
+
if (this.tenure.kind === "held")
|
|
1297
|
+
trackEphemeralHostWorkspace(this.tenure.root);
|
|
1269
1298
|
})
|
|
1270
1299
|
.catch((e) => {
|
|
1271
1300
|
this.preparing = undefined;
|
|
@@ -1305,7 +1334,7 @@ export class RemoteHostExecutionEnv {
|
|
|
1305
1334
|
const rel = path.relative(real(root), real(abs));
|
|
1306
1335
|
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
1307
1336
|
};
|
|
1308
|
-
return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.
|
|
1337
|
+
return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.tenure.kind === "borrowed" && inside(this.workspaceDir));
|
|
1309
1338
|
}
|
|
1310
1339
|
async fsReady(p, opts) {
|
|
1311
1340
|
if (this.destroyed) {
|
|
@@ -1389,11 +1418,39 @@ function asError(e) {
|
|
|
1389
1418
|
function signalNumber(signal) {
|
|
1390
1419
|
return os.constants.signals[signal];
|
|
1391
1420
|
}
|
|
1392
|
-
export function hostExecutionEnvFactory(config = {}) {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1421
|
+
export function hostExecutionEnvFactory(config = {}, legOf = () => ({})) {
|
|
1422
|
+
const custody = createWorkspaceCustody();
|
|
1423
|
+
const heldElsewhere = (self, root) => custody.holder((m) => m !== self && m.heldWorkspace === root) !== undefined;
|
|
1424
|
+
return (ctx) => {
|
|
1425
|
+
const { minted, ...leg } = legOf(ctx);
|
|
1426
|
+
const identity = ctx.taskId ?? ctx.sessionId;
|
|
1427
|
+
return custody.envOf(identity, () => {
|
|
1428
|
+
const dir = leg.workspaceDir ?? config.workspaceDir;
|
|
1429
|
+
const sharedRoot = dir === undefined ? undefined : custody.holder((m) => m.heldWorkspace !== undefined && dirWithin(dir, m.heldWorkspace))?.heldWorkspace;
|
|
1430
|
+
const env = new RemoteHostExecutionEnv({ ...config, ...leg, id: config.id ?? identity, custody: { heldElsewhere, ...(sharedRoot !== undefined ? { sharedRoot } : {}) } });
|
|
1431
|
+
minted?.(env);
|
|
1432
|
+
return env;
|
|
1433
|
+
});
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
function dirWithin(dir, root) {
|
|
1437
|
+
const inside = (d, r) => {
|
|
1438
|
+
const rel = path.relative(r, d);
|
|
1439
|
+
return rel === "" || (rel.split(path.sep)[0] !== ".." && !path.isAbsolute(rel));
|
|
1440
|
+
};
|
|
1441
|
+
return inside(path.resolve(dir), path.resolve(root)) || inside(physicalPathOf(dir), physicalPathOf(root));
|
|
1442
|
+
}
|
|
1443
|
+
function physicalPathOf(p) {
|
|
1444
|
+
let head = path.resolve(p);
|
|
1445
|
+
const tail = [];
|
|
1446
|
+
while (!existsSync(head)) {
|
|
1447
|
+
const up = path.dirname(head);
|
|
1448
|
+
if (up === head)
|
|
1449
|
+
return path.resolve(p);
|
|
1450
|
+
tail.unshift(path.basename(head));
|
|
1451
|
+
head = up;
|
|
1452
|
+
}
|
|
1453
|
+
return path.join(realpathSync(head), ...tail);
|
|
1397
1454
|
}
|
|
1398
1455
|
export { localDockerExecutionEnvFactory, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
|
|
1399
1456
|
//# sourceMappingURL=remote-env-host.js.map
|