@humwick/server 0.130.0 → 0.130.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/docs/guides/tools-administration.md +2 -1
- package/docs/openapi.json +1 -1
- package/node_modules/@humwick/kernel/package.json +0 -8
- package/node_modules/@humwick/offload/package.json +0 -4
- package/package.json +5 -5
- package/public/assets/fleet-B_rFEbnL.js +2 -0
- package/public/assets/{index-BK0WV6hF.js → index-BU3uWo2t.js} +4 -4
- package/public/index.html +1 -1
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/scripts/cd-config.ts +16 -3
- package/src/commands-db.ts +7 -3
- package/src/commands-write.ts +8 -0
- package/src/db/activity-write-ops.ts +1 -0
- package/src/db/agent-lifecycle-write-ops.ts +1 -0
- package/src/db/agent-meta.ts +2 -2
- package/src/db/agent-ownership-write-ops.ts +4 -0
- package/src/db/agents-async.ts +6 -2
- package/src/db/agents-write-ops.ts +16 -2
- package/src/db/artifacts-write-ops.ts +8 -3
- package/src/db/bus-outbox-write-ops.ts +1 -0
- package/src/db/channels-write-ops.ts +4 -0
- package/src/db/claude-api-budget-ops.ts +5 -5
- package/src/db/claude-app-oauth.ts +4 -4
- package/src/db/codex-reset-consumes-write-ops.ts +3 -0
- package/src/db/commands-write-ops.ts +21 -0
- package/src/db/config-store-write-ops.ts +2 -0
- package/src/db/continuation-archives-write-ops.ts +2 -0
- package/src/db/delivery-write-ops.ts +5 -0
- package/src/db/hosts-write-ops.ts +14 -0
- package/src/db/inbox-write-ops.ts +4 -0
- package/src/db/insights-write-ops.ts +1 -0
- package/src/db/macos-privacy.ts +1 -1
- package/src/db/message-reads-write-ops.ts +3 -0
- package/src/db/message-write-ops.ts +4 -0
- package/src/db/orchestrators-async.ts +7 -3
- package/src/db/orchestrators-write-ops.ts +7 -1
- package/src/db/orchestrators.ts +17 -5
- package/src/db/prompt-versions-ops.ts +1 -0
- package/src/db/provider-catalog-write-ops.ts +4 -4
- package/src/db/provider-flap-messages-write-ops.ts +1 -0
- package/src/db/provider-quota-lease-write-ops.ts +1 -0
- package/src/db/provider-quota-write-ops.ts +2 -0
- package/src/db/provisioning-write-ops.ts +9 -8
- package/src/db/routing-policy.ts +1 -1
- package/src/db/secrets-broker-write-ops.ts +5 -5
- package/src/db/secrets-vault-write.ts +7 -7
- package/src/db/services-write-ops.ts +3 -0
- package/src/db/spawn-tree-cleanup.ts +3 -0
- package/src/db/team-spawn-delegation.ts +1 -1
- package/src/db/teams-write-ops.ts +4 -4
- package/src/db/timer-cutover-write-ops.ts +7 -0
- package/src/db/timer-ingress-write-ops.ts +9 -0
- package/src/db/timer-operator-write-ops.ts +14 -0
- package/src/db/timer-transfer-write-ops.ts +4 -0
- package/src/db/timer-write-ops.ts +14 -0
- package/src/db/token-db-write-ops.ts +4 -0
- package/src/db/user-settings-write-ops.ts +1 -0
- package/src/db/vault-backup.ts +2 -2
- package/src/db/vault-expiry.ts +2 -2
- package/src/db/vault-health.ts +3 -3
- package/src/db/vault-token-ops.ts +8 -8
- package/src/db/web-tool-call-ops.ts +3 -3
- package/src/db/write-ops.ts +9 -1
- package/src/db/writer-fault-seam.ts +5 -2
- package/src/db/writer-gateway.ts +4 -1
- package/src/mcp-host-tools.ts +1 -1
- package/src/package-pack.ts +42 -39
- package/src/routes/host-resource-control.ts +4 -1
- package/src/routes/orchestrator-bootstrap.ts +2 -2
- package/src/routes/orchestrator.ts +2 -2
- package/src/routes/provider-config.ts +3 -2
- package/src/runtime-tokens.ts +12 -3
- package/src/services/kernel-timers-cutover-chaos-worker.ts +2 -0
- package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -1
- package/public/assets/fleet-BL4UXS_k.js +0 -2
|
@@ -39,14 +39,17 @@ import {
|
|
|
39
39
|
export const TIMER_OPERATOR_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
40
40
|
// --- Dedup-by-construction: idempotent, no key required. ------------------------------------------
|
|
41
41
|
"timerOperator.acceptCommand": {
|
|
42
|
+
offLoop: true,
|
|
42
43
|
idempotent: true,
|
|
43
44
|
fn: (args: AcceptAuditedCommandArgs, ctx: WriterOpContext) => acceptAuditedCommandBody(args, ctx.now),
|
|
44
45
|
},
|
|
45
46
|
"timerOperator.ensureOperation": {
|
|
47
|
+
offLoop: true,
|
|
46
48
|
idempotent: true,
|
|
47
49
|
fn: (args: Omit<EnsureOperationArgs, "now">, ctx: WriterOpContext) => ensureOperationBody({ ...args, now: ctx.now }),
|
|
48
50
|
},
|
|
49
51
|
"timerOperator.resetTerminal": {
|
|
52
|
+
offLoop: true,
|
|
50
53
|
// An occupied terminal slot deletes a row; replaying its body with `acceptAlreadyCleared: false`
|
|
51
54
|
// throws. The facade therefore supplies a durable request-scoped key for retry replay.
|
|
52
55
|
idempotent: false,
|
|
@@ -54,48 +57,59 @@ export const TIMER_OPERATOR_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
|
|
|
54
57
|
resetTerminalOperationBody(args.requestId, args.acceptAlreadyCleared),
|
|
55
58
|
},
|
|
56
59
|
"timerOperator.finishAbort": {
|
|
60
|
+
offLoop: true,
|
|
57
61
|
idempotent: true,
|
|
58
62
|
fn: (_args: Record<string, never> | undefined, ctx: WriterOpContext) => finishAbortBody(ctx.now),
|
|
59
63
|
},
|
|
60
64
|
"timerOperator.pinTransferLeg": {
|
|
65
|
+
offLoop: true,
|
|
61
66
|
idempotent: true,
|
|
62
67
|
fn: (args: Omit<PinTransferLegArgs, "now">, ctx: WriterOpContext) => pinTransferLegBody({ ...args, now: ctx.now }),
|
|
63
68
|
},
|
|
64
69
|
"timerOperator.finishTransfer": {
|
|
70
|
+
offLoop: true,
|
|
65
71
|
idempotent: true,
|
|
66
72
|
fn: (args: Omit<FinishTransferArgs, "now">, ctx: WriterOpContext) => finishTransferBody({ ...args, now: ctx.now }),
|
|
67
73
|
},
|
|
68
74
|
|
|
69
75
|
// --- No in-body dedup guard: idempotent false, facade supplies a per-call key. --------------------
|
|
70
76
|
"timerOperator.claimOperation": {
|
|
77
|
+
offLoop: true,
|
|
71
78
|
idempotent: false,
|
|
72
79
|
fn: (args: Omit<ClaimOperationArgs, "now">, ctx: WriterOpContext) => claimOperationBody({ ...args, now: ctx.now }),
|
|
73
80
|
},
|
|
74
81
|
"timerOperator.commitBackfillBatch": {
|
|
82
|
+
offLoop: true,
|
|
75
83
|
idempotent: false,
|
|
76
84
|
fn: (args: Omit<CommitBackfillBatchArgs, "at">, ctx: WriterOpContext) => commitBackfillBatchBody({ ...args, at: ctx.now }),
|
|
77
85
|
},
|
|
78
86
|
"timerOperator.epochRebase": {
|
|
87
|
+
offLoop: true,
|
|
79
88
|
idempotent: false,
|
|
80
89
|
fn: (args: Omit<EpochRebaseArgs, "at">, ctx: WriterOpContext) => epochRebaseBody({ ...args, at: ctx.now }),
|
|
81
90
|
},
|
|
82
91
|
"timerOperator.commitBootstrapBatch": {
|
|
92
|
+
offLoop: true,
|
|
83
93
|
idempotent: false,
|
|
84
94
|
fn: (args: Omit<CommitBootstrapBatchArgs, "at">, ctx: WriterOpContext) => commitBootstrapBatchBody({ ...args, at: ctx.now }),
|
|
85
95
|
},
|
|
86
96
|
"timerOperator.completeStage9": {
|
|
97
|
+
offLoop: true,
|
|
87
98
|
idempotent: false,
|
|
88
99
|
fn: (args: Omit<CompleteStage9Args, "at">, ctx: WriterOpContext) => completeStage9Body({ ...args, at: ctx.now }),
|
|
89
100
|
},
|
|
90
101
|
"timerOperator.beginAbort": {
|
|
102
|
+
offLoop: true,
|
|
91
103
|
idempotent: false,
|
|
92
104
|
fn: (args: Omit<BeginAbortArgs, "now">, ctx: WriterOpContext) => beginAbortBody({ ...args, now: ctx.now }),
|
|
93
105
|
},
|
|
94
106
|
"timerOperator.recordG1Transfer": {
|
|
107
|
+
offLoop: true,
|
|
95
108
|
idempotent: false,
|
|
96
109
|
fn: (args: Omit<RecordG1TransferArgs, "now">, ctx: WriterOpContext) => recordG1TransferBody({ ...args, now: ctx.now }),
|
|
97
110
|
},
|
|
98
111
|
"timerOperator.commitTransferBatch": {
|
|
112
|
+
offLoop: true,
|
|
99
113
|
idempotent: false,
|
|
100
114
|
fn: (args: Omit<CommitTransferBatchArgs, "at">, ctx: WriterOpContext) => commitTransferBatchBody({ ...args, at: ctx.now }),
|
|
101
115
|
},
|
|
@@ -21,18 +21,22 @@ function withNow(args: TransferArgs, ctx: WriterOpContext): TimerMessageTransfer
|
|
|
21
21
|
* the same durable journal row or is refused; it cannot move ownership twice. */
|
|
22
22
|
export const TIMER_TRANSFER_WRITE_OPS = {
|
|
23
23
|
"timers.transfer.prepareForward": {
|
|
24
|
+
offLoop: true,
|
|
24
25
|
idempotent: true,
|
|
25
26
|
fn: (args: TransferArgs, ctx: WriterOpContext) => prepareForwardTimerMessageTransfer(withNow(args, ctx)),
|
|
26
27
|
},
|
|
27
28
|
"timers.transfer.prepareReverse": {
|
|
29
|
+
offLoop: true,
|
|
28
30
|
idempotent: true,
|
|
29
31
|
fn: (args: TransferArgs, ctx: WriterOpContext) => prepareReverseTimerMessageTransfer(withNow(args, ctx)),
|
|
30
32
|
},
|
|
31
33
|
"timers.transfer.commitForward": {
|
|
34
|
+
offLoop: true,
|
|
32
35
|
idempotent: true,
|
|
33
36
|
fn: (args: TransferArgs, ctx: WriterOpContext) => commitForwardTimerMessageTransfer(withNow(args, ctx)),
|
|
34
37
|
},
|
|
35
38
|
"timers.transfer.commitReverse": {
|
|
39
|
+
offLoop: true,
|
|
36
40
|
idempotent: true,
|
|
37
41
|
fn: (args: TransferArgs, ctx: WriterOpContext) => commitReverseTimerMessageTransfer(withNow(args, ctx)),
|
|
38
42
|
},
|
|
@@ -136,6 +136,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
136
136
|
* `row.id` as that key: it is unique per logical create by construction and stable across a retry.
|
|
137
137
|
*/
|
|
138
138
|
"timers.create": {
|
|
139
|
+
offLoop: true,
|
|
139
140
|
idempotent: false,
|
|
140
141
|
fn: (args: CreateTimerOpArgs, ctx: WriterOpContext) =>
|
|
141
142
|
insertScheduledTimerWithinQuota(args.row, args.caps, ctx.now, QUOTA_FAULTS, args.route ?? "direct"),
|
|
@@ -143,6 +144,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
143
144
|
|
|
144
145
|
/** CAS claim of a due timer. Re-applying either re-wins the same lease or returns null — idempotent. */
|
|
145
146
|
"timers.claim": {
|
|
147
|
+
offLoop: true,
|
|
146
148
|
idempotent: true,
|
|
147
149
|
fn: (args: ClaimTimerOpArgs, ctx: WriterOpContext) =>
|
|
148
150
|
claimScheduledTimer(args.id, args.owner, args.now ?? ctx.now, args.leaseMs),
|
|
@@ -155,6 +157,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
155
157
|
* matching replay writes the identical row.
|
|
156
158
|
*/
|
|
157
159
|
"timers.completeRun": {
|
|
160
|
+
offLoop: true,
|
|
158
161
|
idempotent: true,
|
|
159
162
|
fn: (args: CompleteTimerRunOpArgs, ctx: WriterOpContext) =>
|
|
160
163
|
completeScheduledTimerRun({ timer: args.timer, messageId: args.messageId, now: args.now ?? ctx.now }),
|
|
@@ -162,6 +165,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
162
165
|
|
|
163
166
|
/** Same claim-predicate shape as `completeRun`, so a replay is a no-op — idempotent. */
|
|
164
167
|
"timers.recordFailure": {
|
|
168
|
+
offLoop: true,
|
|
165
169
|
idempotent: true,
|
|
166
170
|
fn: (args: RecordTimerFailureOpArgs, ctx: WriterOpContext) =>
|
|
167
171
|
recordScheduledTimerFailure({
|
|
@@ -175,6 +179,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
175
179
|
|
|
176
180
|
/** `WHERE enabled = 1` makes the second cancel a no-op that returns the same row — idempotent. */
|
|
177
181
|
"timers.cancel": {
|
|
182
|
+
offLoop: true,
|
|
178
183
|
idempotent: true,
|
|
179
184
|
fn: (args: { id: string; route?: TimerMessageMutationRoute }) =>
|
|
180
185
|
cancelScheduledTimer(args.id, args.route ?? "direct"),
|
|
@@ -182,6 +187,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
182
187
|
|
|
183
188
|
/** Unconditional UPDATE to a fixed end state — re-applying reaches the same state. */
|
|
184
189
|
"timers.disable": {
|
|
190
|
+
offLoop: true,
|
|
185
191
|
idempotent: true,
|
|
186
192
|
fn: (args: { id: string; reason: string }) => disableScheduledTimer(args.id, args.reason),
|
|
187
193
|
},
|
|
@@ -189,6 +195,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
189
195
|
/** UPDATE to a caller-supplied absolute `nextRunAt` — replaying writes the same instant, so it is
|
|
190
196
|
* the same end state. Idempotent. */
|
|
191
197
|
"timers.deferRun": {
|
|
198
|
+
offLoop: true,
|
|
192
199
|
idempotent: true,
|
|
193
200
|
fn: (args: { id: string; nextRunAt: number }) => deferScheduledTimerRun(args.id, args.nextRunAt),
|
|
194
201
|
},
|
|
@@ -196,6 +203,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
196
203
|
/** Read-modify-write of the command JSON, folded into the op. Writing the same commandId twice is
|
|
197
204
|
* the same end state — idempotent. */
|
|
198
205
|
"timers.setCommandId": {
|
|
206
|
+
offLoop: true,
|
|
199
207
|
idempotent: true,
|
|
200
208
|
fn: (args: { timerId: string; commandId: string }) => setScheduledTimerCommandId(args.timerId, args.commandId),
|
|
201
209
|
},
|
|
@@ -203,6 +211,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
203
211
|
/** Read-modify-write merge of a command patch, folded into the op. Re-merging the same patch onto
|
|
204
212
|
* the already-merged row is the same end state — idempotent. */
|
|
205
213
|
"timers.updateCommand": {
|
|
214
|
+
offLoop: true,
|
|
206
215
|
idempotent: true,
|
|
207
216
|
fn: (args: UpdateTimerCommandOpArgs) => updateScheduledTimerCommand(args.timerId, args.patch),
|
|
208
217
|
},
|
|
@@ -214,6 +223,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
214
223
|
* decide the same run twice.
|
|
215
224
|
*/
|
|
216
225
|
"timers.decideFire": {
|
|
226
|
+
offLoop: true,
|
|
217
227
|
idempotent: true,
|
|
218
228
|
fn: (args: { input: Omit<DecideLegacyTimerMessageFireInput, "now">; now?: number }, ctx: WriterOpContext) =>
|
|
219
229
|
decideLegacyTimerMessageFire({ ...args.input, now: args.now ?? ctx.now }, FIRE_DECISION_FAULTS),
|
|
@@ -221,6 +231,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
221
231
|
|
|
222
232
|
/** Returns `already-sent` on replay rather than re-marking — idempotent. */
|
|
223
233
|
"timers.markFireSent": {
|
|
234
|
+
offLoop: true,
|
|
224
235
|
idempotent: true,
|
|
225
236
|
fn: (args: { timerId: string; run: number; messageId: number }, ctx: WriterOpContext) =>
|
|
226
237
|
markTimerMessageFireSent(args.timerId, args.run, args.messageId, ctx.now),
|
|
@@ -232,6 +243,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
232
243
|
* therefore requires a key; the facade passes one derived from the failing attempt.
|
|
233
244
|
*/
|
|
234
245
|
"timers.recordFireSendFailure": {
|
|
246
|
+
offLoop: true,
|
|
235
247
|
idempotent: false,
|
|
236
248
|
fn: (args: RecordFireSendFailureOpArgs, ctx: WriterOpContext) =>
|
|
237
249
|
recordTimerMessageFireSendFailure({
|
|
@@ -246,6 +258,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
246
258
|
|
|
247
259
|
/** DELETE of a transient failure record — re-applying deletes nothing. Idempotent. */
|
|
248
260
|
"timers.clearFireSendFailure": {
|
|
261
|
+
offLoop: true,
|
|
249
262
|
idempotent: true,
|
|
250
263
|
fn: (args: { timerId: string; run: number }) => {
|
|
251
264
|
clearTimerMessageFireSendFailure(args.timerId, args.run);
|
|
@@ -255,6 +268,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
|
|
|
255
268
|
|
|
256
269
|
/** Monotone fence advance (`applied_fence < fence` guard) — re-applying is a no-op. Idempotent. */
|
|
257
270
|
"timers.markAuthorityApplied": {
|
|
271
|
+
offLoop: true,
|
|
258
272
|
idempotent: true,
|
|
259
273
|
fn: (args: { timerId: string; fence: number }, ctx: WriterOpContext) => {
|
|
260
274
|
markLegacyMessageAuthorityApplied(args.timerId, args.fence, ctx.now);
|
|
@@ -67,12 +67,14 @@ function stripHandoffToken(result: unknown): unknown {
|
|
|
67
67
|
export const TOKEN_DB_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
68
68
|
// Non-idempotent (mints a new row each apply); the facade supplies `jti` as the ledger key.
|
|
69
69
|
"tokens.create": {
|
|
70
|
+
offLoop: true,
|
|
70
71
|
idempotent: false,
|
|
71
72
|
fn: (args: CreateTokenOpArgs, ctx: WriterOpContext) => createTokenInternal(args.input, args.jti, ctx.now),
|
|
72
73
|
serializeIdempotencyResult: stripCreateToken,
|
|
73
74
|
},
|
|
74
75
|
// Non-idempotent (mints and links a successor); folds the eligibility read (ADR §3.4).
|
|
75
76
|
"tokens.handoff": {
|
|
77
|
+
offLoop: true,
|
|
76
78
|
idempotent: false,
|
|
77
79
|
fn: (args: HandoffTokenOpArgs, ctx: WriterOpContext) => handoffTokenInternal(args.input, args.newJti, ctx.now),
|
|
78
80
|
serializeIdempotencyResult: stripHandoffToken,
|
|
@@ -80,11 +82,13 @@ export const TOKEN_DB_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
80
82
|
// Idempotent by construction (the `revoked_at IS NULL` guard makes a re-apply a no-op). No key, no
|
|
81
83
|
// secret in the boolean result.
|
|
82
84
|
"tokens.revoke": {
|
|
85
|
+
offLoop: true,
|
|
83
86
|
idempotent: true,
|
|
84
87
|
fn: (args: RevokeTokenOpArgs, ctx: WriterOpContext) => revokeTokenInternal(args.jti, args.reason, ctx.now),
|
|
85
88
|
},
|
|
86
89
|
// Idempotent (deleting an already-reaped row is a no-op). Returns pruned jtis — not secret.
|
|
87
90
|
"tokens.pruneExpired": {
|
|
91
|
+
offLoop: true,
|
|
88
92
|
idempotent: true,
|
|
89
93
|
fn: (args: PruneExpiredTokensOpArgs, ctx: WriterOpContext) =>
|
|
90
94
|
pruneExpiredTokenRecordsInternal(args.input, ctx.now),
|
|
@@ -36,6 +36,7 @@ export const USER_SETTINGS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
|
|
|
36
36
|
},
|
|
37
37
|
// Idempotent by construction: deleting again finds no row (`existing` null → early false).
|
|
38
38
|
"userSettings.delete": {
|
|
39
|
+
offLoop: true,
|
|
39
40
|
idempotent: true,
|
|
40
41
|
fn: (args: DeleteUserSettingOpArgs) => deleteUserSetting(getDb(), args.userId, args.namespace, args.key),
|
|
41
42
|
},
|
package/src/db/vault-backup.ts
CHANGED
|
@@ -54,6 +54,6 @@ export function syncVaultBackupWarning(args: { kind: VaultBackupWarning | null;
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export const VAULT_BACKUP_WRITE_OPS = {
|
|
57
|
-
"secretsVault.backup.record": { idempotent: true, fn: recordVaultBackup },
|
|
58
|
-
"secretsVault.backup.warn": { idempotent: true, fn: syncVaultBackupWarning },
|
|
57
|
+
"secretsVault.backup.record": { offLoop: true, idempotent: true, fn: recordVaultBackup },
|
|
58
|
+
"secretsVault.backup.warn": { offLoop: true, idempotent: true, fn: syncVaultBackupWarning },
|
|
59
59
|
};
|
package/src/db/vault-expiry.ts
CHANGED
|
@@ -66,6 +66,6 @@ export function prepareVaultExpiryNotices(args: { now: number }) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export const VAULT_EXPIRY_WRITE_OPS = {
|
|
69
|
-
"secretsVault.expiry.prepare": { idempotent: true, fn: prepareVaultExpiryNotices },
|
|
70
|
-
"secretsVault.expiry.sent": { idempotent: true, fn: (args: { id: number }) => { getDb().query("UPDATE vault_expiry_notices SET notified=1 WHERE decision_id=?").run(args.id); } },
|
|
69
|
+
"secretsVault.expiry.prepare": { offLoop: true, idempotent: true, fn: prepareVaultExpiryNotices },
|
|
70
|
+
"secretsVault.expiry.sent": { offLoop: true, idempotent: true, fn: (args: { id: number }) => { getDb().query("UPDATE vault_expiry_notices SET notified=1 WHERE decision_id=?").run(args.id); } },
|
|
71
71
|
};
|
package/src/db/vault-health.ts
CHANGED
|
@@ -85,9 +85,9 @@ export function logPage(args: { profile?: string; before?: { at: number; id: num
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export const VAULT_HEALTH_WRITE_OPS: Record<string, WriterOpSpec> = {
|
|
88
|
-
"vaultHealth.host": { idempotent: true, fn: (host: ServiceAccessHostHealth, ctx: WriterOpContext) => { writeState("host", host, ctx.now); return null; } },
|
|
89
|
-
"vaultHealth.collect": { idempotent: true, fn: (input: VaultLogCollect, ctx: WriterOpContext) => collectVaultLogs(input, ctx.now) },
|
|
90
|
-
"vaultHealth.prune": { idempotent: true, fn: (args: { now: number }) => pruneVaultLogs(args.now) },
|
|
88
|
+
"vaultHealth.host": { offLoop: true, idempotent: true, fn: (host: ServiceAccessHostHealth, ctx: WriterOpContext) => { writeState("host", host, ctx.now); return null; } },
|
|
89
|
+
"vaultHealth.collect": { offLoop: true, idempotent: true, fn: (input: VaultLogCollect, ctx: WriterOpContext) => collectVaultLogs(input, ctx.now) },
|
|
90
|
+
"vaultHealth.prune": { offLoop: true, idempotent: true, fn: (args: { now: number }) => pruneVaultLogs(args.now) },
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
export const VAULT_HEALTH_READ_OPS = {
|
|
@@ -3,12 +3,12 @@ import { settleVaultToken, revokeVaultTokens, recordOrphanVaultToken, pruneVault
|
|
|
3
3
|
import type { WriterOpContext, WriterOpSpec } from "./write-ops";
|
|
4
4
|
|
|
5
5
|
export const VAULT_TOKEN_WRITE_OPS: Record<string, WriterOpSpec> = {
|
|
6
|
-
"vaultTokens.orphan": { idempotent: true, fn: (row: VaultTokenRow) => recordOrphanVaultToken(row) },
|
|
7
|
-
"vaultTokens.prune": { idempotent: true, fn: (args: { now: number }) => pruneVaultTokens(args.now) },
|
|
8
|
-
"vaultTokens.begin": { idempotent: false, fn: (row: VaultTokenRow) => beginVaultToken(row) },
|
|
9
|
-
"vaultTokens.activate": { idempotent: true, fn: (args: { id: string; sessionId: string; expiresAt: number }, ctx: WriterOpContext) => activateVaultToken(args.id, args.sessionId, args.expiresAt, ctx.now) },
|
|
10
|
-
"vaultTokens.renew": { idempotent: true, fn: (args: { id: string; sessionId: string; expiresAt: number }, ctx: WriterOpContext) => renewVaultToken(args.id, args.sessionId, args.expiresAt, ctx.now) },
|
|
11
|
-
"vaultTokens.check": { idempotent: true, fn: (args: { id: string; now: number }) => checkVaultToken(args.id, args.now) ?? null },
|
|
12
|
-
"vaultTokens.settle": { idempotent: false, fn: (args: { id: string; error?: string }, ctx: WriterOpContext) => { settleVaultToken(args.id, ctx.now, args.error); return null; } },
|
|
13
|
-
"vaultTokens.revoke": { idempotent: true, fn: (args: { agentId: string; instanceId: string; spawnRequestId: string; launchId: string }, ctx: WriterOpContext) => { revokeVaultTokens(args.agentId, ctx.now, "runner_release", args.instanceId, args.spawnRequestId, args.launchId); return null; } },
|
|
6
|
+
"vaultTokens.orphan": { offLoop: true, idempotent: true, fn: (row: VaultTokenRow) => recordOrphanVaultToken(row) },
|
|
7
|
+
"vaultTokens.prune": { offLoop: true, idempotent: true, fn: (args: { now: number }) => pruneVaultTokens(args.now) },
|
|
8
|
+
"vaultTokens.begin": { offLoop: true, idempotent: false, fn: (row: VaultTokenRow) => beginVaultToken(row) },
|
|
9
|
+
"vaultTokens.activate": { offLoop: true, idempotent: true, fn: (args: { id: string; sessionId: string; expiresAt: number }, ctx: WriterOpContext) => activateVaultToken(args.id, args.sessionId, args.expiresAt, ctx.now) },
|
|
10
|
+
"vaultTokens.renew": { offLoop: true, idempotent: true, fn: (args: { id: string; sessionId: string; expiresAt: number }, ctx: WriterOpContext) => renewVaultToken(args.id, args.sessionId, args.expiresAt, ctx.now) },
|
|
11
|
+
"vaultTokens.check": { offLoop: true, idempotent: true, fn: (args: { id: string; now: number }) => checkVaultToken(args.id, args.now) ?? null },
|
|
12
|
+
"vaultTokens.settle": { offLoop: true, idempotent: false, fn: (args: { id: string; error?: string }, ctx: WriterOpContext) => { settleVaultToken(args.id, ctx.now, args.error); return null; } },
|
|
13
|
+
"vaultTokens.revoke": { offLoop: true, idempotent: true, fn: (args: { agentId: string; instanceId: string; spawnRequestId: string; launchId: string }, ctx: WriterOpContext) => { revokeVaultTokens(args.agentId, ctx.now, "runner_release", args.instanceId, args.spawnRequestId, args.launchId); return null; } },
|
|
14
14
|
};
|
|
@@ -9,7 +9,7 @@ export const WEB_TOOL_CALL_READ_OPS = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const WEB_TOOL_CALL_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
|
|
12
|
-
"webSearch.reserveBrave": { idempotent: false, fn: (args: { month: string }) => reserveBraveSearch(args.month) },
|
|
13
|
-
"webToolCalls.record": { idempotent: true, fn: (call: WebToolCall) => { recordWebToolCall(call); return null; } },
|
|
14
|
-
"webToolCalls.prune": { idempotent: true, fn: (args: { before: number }) => pruneWebToolCalls(args.before) },
|
|
12
|
+
"webSearch.reserveBrave": { offLoop: true, idempotent: false, fn: (args: { month: string }) => reserveBraveSearch(args.month) },
|
|
13
|
+
"webToolCalls.record": { offLoop: true, idempotent: true, fn: (call: WebToolCall) => { recordWebToolCall(call); return null; } },
|
|
14
|
+
"webToolCalls.prune": { offLoop: true, idempotent: true, fn: (args: { before: number }) => pruneWebToolCalls(args.before) },
|
|
15
15
|
};
|
package/src/db/write-ops.ts
CHANGED
|
@@ -260,6 +260,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
260
260
|
// Delete all probe rows — a test reset. Deleting again reaches the same end state, so re-applying is
|
|
261
261
|
// safe: idempotent, no key required.
|
|
262
262
|
"seam.reset": {
|
|
263
|
+
offLoop: true,
|
|
263
264
|
idempotent: true,
|
|
264
265
|
fn: () => {
|
|
265
266
|
const before = (getDb().query("SELECT COUNT(*) AS n FROM _writer_seam_probe").get() as { n: number }).n;
|
|
@@ -286,6 +287,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
286
287
|
// must NOT enter the retry ledger — the retry re-evaluates fresh and can succeed once the blocker
|
|
287
288
|
// clears — while an applied outcome dedups exactly-once as usual.
|
|
288
289
|
"seam.claimLike": {
|
|
290
|
+
offLoop: true,
|
|
289
291
|
idempotent: false,
|
|
290
292
|
fn: (args: { tag: string; refuse?: boolean }, ctx) => {
|
|
291
293
|
if (args.refuse) return { applied: false };
|
|
@@ -301,6 +303,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
301
303
|
// single transaction (or savepoint), the first insert MUST roll back — the atomic-multi-statement
|
|
302
304
|
// proof across the boundary (§3.4). Inserts rows — NON-idempotent, key required.
|
|
303
305
|
"seam.txPair": {
|
|
306
|
+
offLoop: true,
|
|
304
307
|
idempotent: false,
|
|
305
308
|
fn: (args: { a: string; b: string; failSecond?: boolean }, ctx) => {
|
|
306
309
|
const first = getDb()
|
|
@@ -318,6 +321,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
318
321
|
// the main thread as a real `ValidationError` (instanceof true), not a bare `Error`. No state change,
|
|
319
322
|
// so re-applying is safe: idempotent, no key required.
|
|
320
323
|
"seam.throwValidation": {
|
|
324
|
+
offLoop: true,
|
|
321
325
|
idempotent: true,
|
|
322
326
|
fn: (args: { message?: string }) => {
|
|
323
327
|
throw new ValidationError(args.message ?? "seam.throwValidation");
|
|
@@ -327,6 +331,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
327
331
|
// Insert-once keyed by `idem_key` in its OWN args — dedups by construction (`ON CONFLICT DO
|
|
328
332
|
// NOTHING`), so it is idempotent and needs no gateway key.
|
|
329
333
|
"seam.idempotent": {
|
|
334
|
+
offLoop: true,
|
|
330
335
|
idempotent: true,
|
|
331
336
|
fn: (args: { key: string; value?: string }, ctx) => {
|
|
332
337
|
const inserted = getDb()
|
|
@@ -347,6 +352,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
347
352
|
// marshaling seam (H1) clones args before the op and clones the result after, so the caller's object
|
|
348
353
|
// is NEVER mutated and the caller sees only the cloned result — as across a real worker boundary.
|
|
349
354
|
"seam.mutateArg": {
|
|
355
|
+
offLoop: true,
|
|
350
356
|
idempotent: true,
|
|
351
357
|
fn: (args: { n: number }) => {
|
|
352
358
|
args.n += 1000;
|
|
@@ -358,6 +364,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
358
364
|
// integration suite to force a writer-job deadline (H4 wedged-writer): Atomics.wait on a private
|
|
359
365
|
// buffer is a true synchronous sleep the gateway deadline fires behind. No write, so idempotent.
|
|
360
366
|
"seam.sleep": {
|
|
367
|
+
offLoop: true,
|
|
361
368
|
idempotent: true,
|
|
362
369
|
fn: (args: { ms: number }) => {
|
|
363
370
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(0, args.ms));
|
|
@@ -368,6 +375,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
368
375
|
// A writer-side read of committed state, for tests to verify persistence without a reader gateway
|
|
369
376
|
// (the writer reading its own connection trivially sees its own writes — §3.5). Read-only: idempotent.
|
|
370
377
|
"seam.snapshot": {
|
|
378
|
+
offLoop: true,
|
|
371
379
|
idempotent: true,
|
|
372
380
|
fn: () => {
|
|
373
381
|
const rows = getDb().query("SELECT id, tag, value, idem_key FROM _writer_seam_probe ORDER BY id").all();
|
|
@@ -450,7 +458,7 @@ export function allWriterOps(): Readonly<Record<string, WriterOpSpec>> {
|
|
|
450
458
|
...VAULT_WRITE_OPS,
|
|
451
459
|
...VAULT_EXPIRY_WRITE_OPS,
|
|
452
460
|
...VAULT_BACKUP_WRITE_OPS,
|
|
453
|
-
"secretsVault.import": { idempotent: false, fn: importVaultRows },
|
|
461
|
+
"secretsVault.import": { offLoop: true, idempotent: false, fn: importVaultRows },
|
|
454
462
|
...MACOS_PRIVACY_WRITE_OPS,
|
|
455
463
|
...PROVIDER_FLAP_MESSAGES_WRITE_OPS,
|
|
456
464
|
...PROVIDER_CATALOG_WRITE_OPS,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { mainThreadRegistry } from "./main-thread-state";
|
|
2
1
|
// The writer-side fault-injection seam for relocated db ops (ADR 0001 §3.1 H6, S3 leaf L-C #1899).
|
|
3
2
|
//
|
|
4
3
|
// WHY THIS EXISTS. Several timer ops carry test-only crash hooks that must fire INSIDE the op's
|
|
@@ -37,7 +36,11 @@ interface ArmedWriterFault {
|
|
|
37
36
|
readonly threadId: number;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
// Deliberately NOT a mainThreadRegistry (#1870 S6a): off-loop op bodies call `writerFault`, and on the
|
|
40
|
+
// writer thread this map is empty, so the lookup is a harmless no-op there. Delivery is kept honest at
|
|
41
|
+
// dispatch: while a fault is armed the in-process backend runs off-loop ops on the arming thread
|
|
42
|
+
// (writer-gateway `dispatchEntry`), and arming refuses under the worker backend.
|
|
43
|
+
const armed = new Map<string, ArmedWriterFault>();
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* Whether a main-thread fault closure can still reach an op body. False once the gateway stands up
|
package/src/db/writer-gateway.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { WriterDrain } from "./writer-drain";
|
|
|
43
43
|
import { errMessage } from "@humwick/sdk";
|
|
44
44
|
import { createWorkerPool, type WorkerPool } from "@humwick/offload";
|
|
45
45
|
import { getDbGeneration, getDbPath } from "./connection.ts";
|
|
46
|
+
import { armedWriterFaultCount } from "./writer-fault-seam.ts";
|
|
46
47
|
import { onLoopWriteTransaction } from "./on-loop-write-transaction.ts";
|
|
47
48
|
// On-loop-block attribution (#1882 / ADR §5). `resolveLoopCallsite` pins the effective callsite onto a
|
|
48
49
|
// submission in the CALLER's context; `captureSyncFrame` times the op body where it actually runs — in
|
|
@@ -551,7 +552,9 @@ async function dispatchEntry(entry: Submission): Promise<void> {
|
|
|
551
552
|
back.busy = true;
|
|
552
553
|
try {
|
|
553
554
|
const path = getDbPath();
|
|
554
|
-
|
|
555
|
+
// An armed crash seam is a closure on THIS thread, so while one is armed the op runs here instead
|
|
556
|
+
// of on the file-DB worker (tests only; production never arms). Otherwise the fault would never fire.
|
|
557
|
+
if (path && path !== ":memory:" && isOffLoopWriterOp(entry.op) && armedWriterFaultCount() === 0) {
|
|
555
558
|
if (back.offLoop && back.offLoopGeneration !== getDbGeneration()) {
|
|
556
559
|
await back.offLoop.pool.destroy();
|
|
557
560
|
back.offLoop = undefined;
|
package/src/mcp-host-tools.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const HOST_TOOLS: ToolDefinition[] = [{
|
|
|
35
35
|
landSlots: { anyOf: [{ type: "integer", minimum: 1, maximum: 1000 }, { type: "null" }] },
|
|
36
36
|
validationSlots: { anyOf: [{ type: "integer", minimum: 1, maximum: 1000 }, { type: "null" }] },
|
|
37
37
|
runCpus: { anyOf: [{ type: "number", minimum: 0.01, maximum: 1000 }, { type: "null" }] },
|
|
38
|
-
runMemory: { anyOf: [{ type: "integer", minimum: 1 }, { type: "null" }], description: "
|
|
38
|
+
runMemory: { anyOf: [{ type: "integer", minimum: 1 }, { type: "null" }], description: "Soft per-run memory, bytes" },
|
|
39
39
|
projectGateReservations: { anyOf: [{ type: "object", additionalProperties: { type: "integer", minimum: 1, maximum: 1000 } }, { type: "null" }] },
|
|
40
40
|
},
|
|
41
41
|
},
|
package/src/package-pack.ts
CHANGED
|
@@ -131,6 +131,42 @@ export function privateWorkspaceDependencies(packageDir: string): string[] {
|
|
|
131
131
|
return privateDeps;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
type DependencyMap = Record<string, string>;
|
|
135
|
+
export type ConsumerManifest = {
|
|
136
|
+
dependencies?: DependencyMap;
|
|
137
|
+
optionalDependencies?: DependencyMap;
|
|
138
|
+
bundleDependencies?: string[];
|
|
139
|
+
};
|
|
140
|
+
export type VendoredManifest = {
|
|
141
|
+
version?: string;
|
|
142
|
+
dependencies?: DependencyMap;
|
|
143
|
+
optionalDependencies?: DependencyMap;
|
|
144
|
+
peerDependencies?: DependencyMap;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Pin and bundle a vendored private dep on its consumer (bundleDependencies keeps the nested copy
|
|
148
|
+
// across reinstall/prune, #1772), and move the dep's own dependencies onto the consumer. npm counts
|
|
149
|
+
// every dependency a bundled package declares as bundled too and never extracts it, so a global
|
|
150
|
+
// install left @humwick/sdk, hono, zod … as empty folders (#3301). The vendored code still resolves
|
|
151
|
+
// them by walking up node_modules.
|
|
152
|
+
export function bundleVendoredDependency(consumer: ConsumerManifest, depName: string, vendored: VendoredManifest): void {
|
|
153
|
+
delete consumer.optionalDependencies?.[depName];
|
|
154
|
+
consumer.dependencies ??= {};
|
|
155
|
+
consumer.dependencies[depName] = vendored.version ?? "0.0.0";
|
|
156
|
+
consumer.bundleDependencies ??= [];
|
|
157
|
+
if (!consumer.bundleDependencies.includes(depName)) consumer.bundleDependencies.push(depName);
|
|
158
|
+
for (const field of ["dependencies", "optionalDependencies", "peerDependencies"] as const) {
|
|
159
|
+
for (const [name, spec] of Object.entries(vendored[field] ?? {})) {
|
|
160
|
+
const declared = consumer.dependencies[name] ?? consumer.optionalDependencies?.[name];
|
|
161
|
+
if (declared !== undefined && declared !== spec) {
|
|
162
|
+
fail(`package-pack: bundled ${depName} needs ${name}@${spec} but its consumer declares ${name}@${declared}; align the two specs`);
|
|
163
|
+
}
|
|
164
|
+
if (declared === undefined) consumer.dependencies[name] = spec;
|
|
165
|
+
}
|
|
166
|
+
delete vendored[field];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
134
170
|
// Vendor a PRIVATE (unpublished, `workspace:*`) workspace dependency into the packed
|
|
135
171
|
// tarball as a nested `node_modules/<dep>` package, pin its bundled version in
|
|
136
172
|
// the packed manifest, and merge its own runtime dependencies up into the consumer so
|
|
@@ -148,31 +184,19 @@ async function bundlePrivateWorkspaceDeps(cwd: string, packageDir: string, tarba
|
|
|
148
184
|
try {
|
|
149
185
|
tar(["-xzf", tarball, "-C", stage]);
|
|
150
186
|
const pkgRoot = join(stage, "package");
|
|
151
|
-
const packed = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")) as
|
|
152
|
-
dependencies?: Record<string, string>;
|
|
153
|
-
optionalDependencies?: Record<string, string>;
|
|
154
|
-
bundleDependencies?: string[];
|
|
155
|
-
};
|
|
187
|
+
const packed = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")) as ConsumerManifest;
|
|
156
188
|
|
|
157
189
|
for (const depName of privateDeps) {
|
|
158
190
|
const depDir = dirByName.get(depName);
|
|
159
191
|
if (!depDir) fail(`package-pack: private workspace dep ${depName} not found in root workspaces`);
|
|
160
192
|
const depSrc = resolve(cwd, depDir);
|
|
161
|
-
const depManifest = JSON.parse(readFileSync(join(depSrc, "package.json"), "utf8")) as {
|
|
162
|
-
version?: string;
|
|
163
|
-
dependencies?: Record<string, string>;
|
|
164
|
-
};
|
|
165
193
|
|
|
166
194
|
// Vendor the private package's REAL published file set by packing it exactly as it would be
|
|
167
195
|
// published — `npm pack` honors the dep's own `files`/`exports` and runs its `prepack` build —
|
|
168
196
|
// then extract the packed `package/` into node_modules/<dep>. Copying only flat root *.ts (the
|
|
169
197
|
// pre-#1838 behavior) silently shipped a package.json-ONLY stub for any dep whose entrypoint is
|
|
170
|
-
// a BUILT dist or lives in a nested dir
|
|
171
|
-
//
|
|
172
|
-
// import with "Cannot find package '@humwick/offload'". Packing is the canonical publish
|
|
173
|
-
// representation and works for BOTH a raw-.ts dep (@humwick/kernel, exports ./index.ts) and a
|
|
174
|
-
// built-dist dep (@humwick/offload) — and for any future published-package offload adopter
|
|
175
|
-
// (orchestrator #1807, later waves) with no per-dep special-casing here.
|
|
198
|
+
// a BUILT dist or lives in a nested dir (@humwick/offload's `main` is ./dist/index.js). Packing
|
|
199
|
+
// works for BOTH a raw-.ts dep (@humwick/kernel) and a built-dist dep with no special-casing.
|
|
176
200
|
const vendorDir = join(pkgRoot, "node_modules", depName);
|
|
177
201
|
const depStage = mkdtempSync(join(packDir, "dep-pack-"));
|
|
178
202
|
try {
|
|
@@ -186,30 +210,9 @@ async function bundlePrivateWorkspaceDeps(cwd: string, packageDir: string, tarba
|
|
|
186
210
|
} finally {
|
|
187
211
|
rmSync(depStage, { recursive: true, force: true });
|
|
188
212
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// package: the nested node_modules/<dep> is treated as an intentional, protected part of
|
|
193
|
-
// the tarball — preserved on install, never fetched from the registry (bundled wins over
|
|
194
|
-
// the pinned version), and — critically — never pruned as "extraneous" on a second
|
|
195
|
-
// `npm install` / `npm ci` / dedupe / prune pass. Merely DROPPING the workspace:* spec
|
|
196
|
-
// (the pre-#1772 behavior) let the FIRST install keep the vendored copy but any later
|
|
197
|
-
// tree reconciliation deleted node_modules/<dep> entirely, breaking every
|
|
198
|
-
// `<dep>/<subpath>` import at runtime (e.g. @humwick/kernel/workflow-journal-codec
|
|
199
|
-
// → CLI crash at startup). See #1772.
|
|
200
|
-
for (const field of ["dependencies", "optionalDependencies"] as const) {
|
|
201
|
-
if (packed[field]) delete packed[field][depName];
|
|
202
|
-
}
|
|
203
|
-
packed.dependencies ??= {};
|
|
204
|
-
packed.dependencies[depName] = depManifest.version ?? "0.0.0";
|
|
205
|
-
packed.bundleDependencies ??= [];
|
|
206
|
-
if (!packed.bundleDependencies.includes(depName)) packed.bundleDependencies.push(depName);
|
|
207
|
-
|
|
208
|
-
// Hoist the private dep's own runtime deps so the clean-env install resolves them from
|
|
209
|
-
// the registry (kernel imports zod/hono/…).
|
|
210
|
-
for (const [name, spec] of Object.entries(depManifest.dependencies ?? {})) {
|
|
211
|
-
packed.dependencies[name] ??= spec;
|
|
212
|
-
}
|
|
213
|
+
const vendored = JSON.parse(readFileSync(join(vendorDir, "package.json"), "utf8")) as VendoredManifest;
|
|
214
|
+
bundleVendoredDependency(packed, depName, vendored);
|
|
215
|
+
writeFileSync(join(vendorDir, "package.json"), JSON.stringify(vendored, null, 2) + "\n");
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
writeFileSync(join(pkgRoot, "package.json"), JSON.stringify(packed, null, 2) + "\n");
|
|
@@ -38,7 +38,10 @@ export function cleanHostResourceControl(value: unknown): HostGateResourceContro
|
|
|
38
38
|
})) as Partial<Record<HostGateKind, HostGateKindStatus>> : undefined;
|
|
39
39
|
return { ...(kinds && HOST_GATE_KINDS.every(name => kinds[name]) ? { kinds: kinds as Record<HostGateKind, HostGateKindStatus> } : {}),
|
|
40
40
|
...(count(value.iterateOverflow) ? { iterateOverflow: value.iterateOverflow as number } : {}),
|
|
41
|
-
...(
|
|
41
|
+
...Object.fromEntries((["gateMemoryBytes", "gateMemoryUsedBytes", "runMemoryBytes"] as const).flatMap(key => {
|
|
42
|
+
const bytes = value[key];
|
|
43
|
+
return typeof bytes === "number" && Number.isSafeInteger(bytes) && bytes >= 0 ? [[key, bytes]] : [];
|
|
44
|
+
})), ...(gateLimits ? { gateLimits } : {}), ...(reservations ? { projectGateReservations: reservations } : {}), ...(work ? { work, workTruncated: value.workTruncated === true || (value.work as unknown[]).length > 50 } : {}),
|
|
42
45
|
...mechanism, usableCpus: usableCpus as number, gateCpus: gateCpus as number,
|
|
43
46
|
memoryBytes: memoryBytes as number, slots: slots as number, held: held as number, waiters: waiters as number,
|
|
44
47
|
...(paths ? { paths } : {}) };
|
|
@@ -6,7 +6,7 @@ import { authorizeRoute, cleanSafeNumber, error, json, parseBody, type Handler }
|
|
|
6
6
|
import { cleanString, cleanStringArray } from "../validation";
|
|
7
7
|
import { createTokenAsync, revokeTokenAsync } from "../token-db-write";
|
|
8
8
|
import { getComponentAuth } from "../security";
|
|
9
|
-
import {
|
|
9
|
+
import { issueOrchestratorRuntimeTokenAsync } from "../runtime-tokens";
|
|
10
10
|
import { type SpawnProvider } from "../types";
|
|
11
11
|
|
|
12
12
|
export const postOrchestratorBootstrap: Handler = async (req) => {
|
|
@@ -80,7 +80,7 @@ export const postOrchestratorBootstrapExchange: Handler = async (req) => {
|
|
|
80
80
|
if (denied) return denied;
|
|
81
81
|
const auth = getComponentAuth(req);
|
|
82
82
|
if (!auth || auth.role !== "orchestrator-bootstrap") return error("bootstrap token required", 403);
|
|
83
|
-
const runtimeToken =
|
|
83
|
+
const runtimeToken = await issueOrchestratorRuntimeTokenAsync({
|
|
84
84
|
orchestratorId: id,
|
|
85
85
|
baseDir,
|
|
86
86
|
createdBy: auth.jti ? `bootstrap:${auth.jti}` : "bootstrap",
|
|
@@ -18,7 +18,7 @@ import { cleanMeta, cleanString, cleanStringArray, cleanWorkspaceMetadata, optio
|
|
|
18
18
|
import { createCommandAsync } from "../commands-write";
|
|
19
19
|
import { emitAgentStatus, emitOrchestratorStatus } from "../sse";
|
|
20
20
|
import { getLifecycleManager } from "../lifecycle-manager";
|
|
21
|
-
import {
|
|
21
|
+
import { issueOrchestratorRuntimeTokenAsync } from "../runtime-tokens";
|
|
22
22
|
import { authContextFromRequest } from "../services/auth-context";
|
|
23
23
|
import { serializeCommandForRead } from "../services/dispatch-command";
|
|
24
24
|
import { isOrchestratorRegistrationAuthorized } from "../security";
|
|
@@ -198,7 +198,7 @@ export const postOrchestrator: Handler = async (req) => {
|
|
|
198
198
|
metadata: { orchestratorId: id, providers: orch.providers, ...authAuditMetadata(req) },
|
|
199
199
|
});
|
|
200
200
|
const runtimeToken = isRootCredentialRequest(req)
|
|
201
|
-
?
|
|
201
|
+
? await issueOrchestratorRuntimeTokenAsync({ orchestratorId: id, baseDir, createdBy: `orchestrator:${id}` })
|
|
202
202
|
: undefined;
|
|
203
203
|
const hostProviders = await hostProviderSync(id, envProviders);
|
|
204
204
|
return json({ ...orch, ...(runtimeToken ? { runtimeToken } : {}), hostProviders, gateBudget: gateBudgetForOrchestrator(id) }, 201);
|
|
@@ -3,7 +3,8 @@ import { ValidationError, effectiveProviderCatalogListAsync, listOrchestrators,
|
|
|
3
3
|
import { chooseProviderDefaultModel } from "../services/provider-catalog";
|
|
4
4
|
import { applyCommandToRecipe } from "../recipe-runner";
|
|
5
5
|
import { cleanString, cleanStringArray, optionalEnum } from "../validation";
|
|
6
|
-
import {
|
|
6
|
+
import { getCommand } from "../commands-db";
|
|
7
|
+
import { deleteCommandAsync } from "../commands-write";
|
|
7
8
|
import { emitCommand, error, json, parseBody, type Handler } from "./_shared";
|
|
8
9
|
import { getProviderConfigEntry, listProviderConfigEntries, migrateLocalProviderConfigs, providerConfigPublic, setProviderConfig, VALID_PROVIDER_CONFIGS } from "../provider-config-store";
|
|
9
10
|
import { cleanProviderModelLimits } from "../provider-catalog-store";
|
|
@@ -222,7 +223,7 @@ function optionalFraction(value: unknown, field: string): number | undefined {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
export const deleteCommandById: Handler = async (_req, params) => {
|
|
225
|
-
if (!
|
|
226
|
+
if (!await deleteCommandAsync(params.id!)) return error("command not found or cannot be canceled", 404);
|
|
226
227
|
const command = getCommand(params.id!);
|
|
227
228
|
if (command) {
|
|
228
229
|
await applyCommandToRecipe(command);
|