@the-open-engine/zeroshot 6.24.0 → 6.25.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/cli/index.js +37 -2
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +37 -11
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-driver.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-driver.js +27 -2
- package/lib/agent-cli-provider/omp-rpc-driver.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-session.js +3 -3
- package/lib/agent-cli-provider/omp-rpc-session.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +7 -1
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +2 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +66 -2
- package/src/agent/agent-task-executor.js +72 -3
- package/src/agent/provider-session.js +111 -2
- package/src/agent-cli-provider/adapters/omp.ts +41 -11
- package/src/agent-cli-provider/omp-rpc-driver.ts +31 -3
- package/src/agent-cli-provider/omp-rpc-session.ts +3 -3
- package/src/agent-cli-provider/provider-registry.ts +7 -1
- package/src/agent-cli-provider/types.ts +4 -0
- package/src/omp-blob-root.js +110 -0
- package/src/omp-config-overlay.js +9 -1
- package/src/omp-execution-fingerprint.js +62 -0
- package/src/omp-session-limits.js +17 -0
- package/src/omp-session-partition.js +297 -0
- package/src/omp-session-verifier.js +576 -0
- package/task-lib/commands/clean.js +23 -0
- package/task-lib/commands/resume.js +42 -0
- package/task-lib/commands/run.js +65 -0
- package/task-lib/omp-session-cleanup.js +160 -0
- package/task-lib/omp-session-ownership-schema.js +262 -0
- package/task-lib/omp-session-ownership.js +332 -0
- package/task-lib/omp-storage-root.js +35 -0
- package/task-lib/rpc-watcher.js +332 -2
- package/task-lib/runner.js +195 -4
- package/task-lib/store.js +42 -7
package/task-lib/runner.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { fork } from 'child_process';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
2
|
+
import { join, dirname, resolve as resolvePath } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { mkdirSync } from 'fs';
|
|
4
5
|
import { LOGS_DIR } from './config.js';
|
|
5
6
|
import { addTask, generateId, ensureDirs } from './store.js';
|
|
7
|
+
import {
|
|
8
|
+
isOmpSessionlessRun,
|
|
9
|
+
resolveOmpStorageRoot,
|
|
10
|
+
resolveOmpOwnerKind,
|
|
11
|
+
} from './omp-storage-root.js';
|
|
12
|
+
import { readOwnership, writeProvisionalOwnership } from './omp-session-ownership.js';
|
|
6
13
|
import { createRequire } from 'module';
|
|
7
14
|
|
|
8
15
|
const require = createRequire(import.meta.url);
|
|
9
16
|
const {
|
|
10
17
|
buildOmpPrompt,
|
|
11
18
|
getProviderRegistryEntry,
|
|
19
|
+
normalizeProviderName,
|
|
12
20
|
prepareSingleAgentProviderCommand,
|
|
13
21
|
} = require('./provider-helper-runtime.js');
|
|
22
|
+
const { getDefaultProviderId } = require('../lib/provider-names.js');
|
|
14
23
|
const {
|
|
15
24
|
ISOLATED_SETTINGS_FILE_ENV,
|
|
16
25
|
ISOLATED_SETTINGS_FILE_MARKER,
|
|
@@ -23,6 +32,11 @@ const {
|
|
|
23
32
|
} = require('../src/worktree-claude-config');
|
|
24
33
|
const { TASK_SPAWN_OWNERSHIP_TOKEN_ENV } = require('../src/task-spawn-cleanup-ownership');
|
|
25
34
|
const { sendWatcherPrompt } = require('../src/watcher-prompt-channel');
|
|
35
|
+
const {
|
|
36
|
+
generateOmpPartitionId,
|
|
37
|
+
partitionPathFor,
|
|
38
|
+
createOmpSessionPartitionDirectory,
|
|
39
|
+
} = require('../src/omp-session-partition');
|
|
26
40
|
export {
|
|
27
41
|
isOwnedProcessTreeRunning,
|
|
28
42
|
isProcessRunning,
|
|
@@ -32,6 +46,162 @@ export {
|
|
|
32
46
|
|
|
33
47
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Cross-check the caller-supplied resume descriptor against the prior owner's *persisted* record
|
|
51
|
+
* and return the expectation the watcher will re-verify.
|
|
52
|
+
*
|
|
53
|
+
* The descriptor arrives over argv (from the agent's own `providerSession.ompSession` snapshot, or
|
|
54
|
+
* from `zeroshot task resume`), so it is never trusted on its own: the task row named by
|
|
55
|
+
* `priorOwnerTaskId` is the authority, and every field the descriptor asserts must match it
|
|
56
|
+
* exactly. A descriptor and a row that disagree are conflicting identities and fail closed here,
|
|
57
|
+
* before a task row is even created — the "conflicting IDs never reach a resume prompt" case.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveOmpResumeExpectation({ descriptor, storageRoot, canonicalWorkspace }) {
|
|
60
|
+
const prior = readOwnership(descriptor.priorOwnerTaskId);
|
|
61
|
+
if (!prior) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`OMP resume: task ${descriptor.priorOwnerTaskId} has no valid OMP session ownership record.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (prior.state !== 'committed' || !prior.session || !prior.partitionIdentity) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`OMP resume: task ${descriptor.priorOwnerTaskId} ownership is '${prior.state}', not a committed resumable session.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (prior.storageRoot !== resolvePath(storageRoot)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`OMP resume: storage root ${resolvePath(storageRoot)} does not match the recorded ${prior.storageRoot}.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
// Moved/deleted workspace and "existing-but-wrong recorded cwd": a session belongs to the
|
|
77
|
+
// workspace it was recorded against and may never be continued from a different one.
|
|
78
|
+
if (prior.canonicalWorkspace !== resolvePath(canonicalWorkspace)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`OMP resume: workspace ${resolvePath(canonicalWorkspace)} does not match the recorded ${prior.canonicalWorkspace}.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const mismatches = [];
|
|
85
|
+
const requireExact = (label, actual, expected) => {
|
|
86
|
+
if (actual !== expected) mismatches.push(`${label} (${actual} != ${expected})`);
|
|
87
|
+
};
|
|
88
|
+
requireExact('partitionId', descriptor.partitionId, prior.partitionId);
|
|
89
|
+
requireExact('sessionId', descriptor.expectedSessionId, prior.session.sessionId);
|
|
90
|
+
requireExact('sessionFileName', descriptor.sessionFileName, prior.session.fileName);
|
|
91
|
+
requireExact(
|
|
92
|
+
'sessionFileIdentity',
|
|
93
|
+
`${descriptor.expectedSessionFileIdentity?.device}:${descriptor.expectedSessionFileIdentity?.inode}`,
|
|
94
|
+
`${prior.session.fileIdentity.device}:${prior.session.fileIdentity.inode}`
|
|
95
|
+
);
|
|
96
|
+
// partitionIdentity is deliberately absent from the agent's `providerSession.ompSession`
|
|
97
|
+
// snapshot (issue #866 fixes that field list, and the snapshot never carries partition paths or
|
|
98
|
+
// storage-root state), so it is authoritative from the row only. `zeroshot task resume`, which
|
|
99
|
+
// reads the row directly, does assert it — check it whenever it is supplied.
|
|
100
|
+
if (descriptor.expectedPartitionIdentity !== undefined) {
|
|
101
|
+
requireExact(
|
|
102
|
+
'partitionIdentity',
|
|
103
|
+
`${descriptor.expectedPartitionIdentity?.device}:${descriptor.expectedPartitionIdentity?.inode}`,
|
|
104
|
+
`${prior.partitionIdentity.device}:${prior.partitionIdentity.inode}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
requireExact(
|
|
108
|
+
'artifactManifestDigest',
|
|
109
|
+
descriptor.expectedArtifactManifestDigest,
|
|
110
|
+
prior.session.artifactManifestDigest
|
|
111
|
+
);
|
|
112
|
+
requireExact(
|
|
113
|
+
'executionFingerprint',
|
|
114
|
+
descriptor.expectedExecutionFingerprint,
|
|
115
|
+
prior.session.executionFingerprint
|
|
116
|
+
);
|
|
117
|
+
requireExact(
|
|
118
|
+
'selectedProvider',
|
|
119
|
+
descriptor.expectedSelectedProvider,
|
|
120
|
+
prior.session.selectedProvider
|
|
121
|
+
);
|
|
122
|
+
requireExact('selectedModel', descriptor.expectedSelectedModel, prior.session.selectedModel);
|
|
123
|
+
if (mismatches.length > 0) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`OMP resume: descriptor conflicts with the persisted owner record: ${mismatches.join(', ')}.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
priorOwnerTaskId: descriptor.priorOwnerTaskId,
|
|
131
|
+
partitionId: prior.partitionId,
|
|
132
|
+
partitionPath: prior.partitionPath,
|
|
133
|
+
canonicalWorkspace: prior.canonicalWorkspace,
|
|
134
|
+
sessionFileName: prior.session.fileName,
|
|
135
|
+
sessionFilePath: join(prior.partitionPath, prior.session.fileName),
|
|
136
|
+
expectedSessionId: prior.session.sessionId,
|
|
137
|
+
expectedPartitionIdentity: prior.partitionIdentity,
|
|
138
|
+
expectedSessionFileIdentity: prior.session.fileIdentity,
|
|
139
|
+
expectedArtifactManifestDigest: prior.session.artifactManifestDigest,
|
|
140
|
+
expectedExecutionFingerprint: prior.session.executionFingerprint,
|
|
141
|
+
expectedSelectedProvider: prior.session.selectedProvider,
|
|
142
|
+
expectedSelectedModel: prior.session.selectedModel,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolve this task's OMP session plan, or null for every other provider / structured-output
|
|
148
|
+
* recovery turn. Allocates (but does not yet create on disk) a fresh partition, or resolves the
|
|
149
|
+
* resume descriptor against the prior owner's persisted record. The partition directory itself is
|
|
150
|
+
* created only after the task row is durable (see spawnTask below — row-before-directory), and
|
|
151
|
+
* the structural/identity/fingerprint verification plus the owner transfer happen in the rpc-stdio
|
|
152
|
+
* watcher, not here.
|
|
153
|
+
*/
|
|
154
|
+
function resolveOmpSessionPlan({ id, cwd, options }) {
|
|
155
|
+
if (options.structuredOutputRecovery) return null;
|
|
156
|
+
const providerName = normalizeProviderName(options.provider || getDefaultProviderId());
|
|
157
|
+
if (providerName !== 'omp') return null;
|
|
158
|
+
// Docker stays fresh-only (issue #866). Returning null here means no partition is allocated, no
|
|
159
|
+
// ownership row is written, and the adapter falls back to `--no-session`.
|
|
160
|
+
if (isOmpSessionlessRun(options)) return null;
|
|
161
|
+
|
|
162
|
+
const storageRoot = resolveOmpStorageRoot(options);
|
|
163
|
+
mkdirSync(storageRoot, { recursive: true });
|
|
164
|
+
const ownerKind = resolveOmpOwnerKind(options);
|
|
165
|
+
const owner = { ...ownerKind, taskId: id };
|
|
166
|
+
|
|
167
|
+
if (options.ompResume) {
|
|
168
|
+
const expectation = resolveOmpResumeExpectation({
|
|
169
|
+
descriptor: options.ompResume,
|
|
170
|
+
storageRoot,
|
|
171
|
+
canonicalWorkspace: cwd,
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
session: {
|
|
175
|
+
kind: 'resume',
|
|
176
|
+
partition: { path: expectation.partitionPath },
|
|
177
|
+
file: { path: expectation.sessionFilePath },
|
|
178
|
+
},
|
|
179
|
+
resumeExpectation: expectation,
|
|
180
|
+
provisionalOwnership: writeProvisionalOwnership({
|
|
181
|
+
partitionId: expectation.partitionId,
|
|
182
|
+
storageRoot,
|
|
183
|
+
canonicalWorkspace: cwd,
|
|
184
|
+
owner,
|
|
185
|
+
}),
|
|
186
|
+
createDirectory: () => {}, // must already exist; the watcher verifies before spawn
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const partitionId = generateOmpPartitionId();
|
|
191
|
+
const partitionPath = partitionPathFor(storageRoot, partitionId);
|
|
192
|
+
return {
|
|
193
|
+
session: { kind: 'fresh', partition: { path: partitionPath } },
|
|
194
|
+
resumeExpectation: null,
|
|
195
|
+
provisionalOwnership: writeProvisionalOwnership({
|
|
196
|
+
partitionId,
|
|
197
|
+
storageRoot,
|
|
198
|
+
canonicalWorkspace: cwd,
|
|
199
|
+
owner,
|
|
200
|
+
}),
|
|
201
|
+
createDirectory: () => createOmpSessionPartitionDirectory(partitionPath),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
35
205
|
export function spawnTask(prompt, options = {}) {
|
|
36
206
|
ensureDirs();
|
|
37
207
|
|
|
@@ -41,10 +211,12 @@ export function spawnTask(prompt, options = {}) {
|
|
|
41
211
|
|
|
42
212
|
const outputFormat = resolveOutputFormat(options);
|
|
43
213
|
const jsonSchema = resolveJsonSchema(options, outputFormat);
|
|
214
|
+
const ompPlan = resolveOmpSessionPlan({ id, cwd, options });
|
|
44
215
|
const prepared = prepareTaskProviderCommandFromResolved(prompt, options, {
|
|
45
216
|
outputFormat,
|
|
46
217
|
jsonSchema,
|
|
47
218
|
cwd,
|
|
219
|
+
ompSession: ompPlan?.session,
|
|
48
220
|
});
|
|
49
221
|
const providerName = prepared.adapter.id;
|
|
50
222
|
const modelSpec = prepared.options.modelSpec;
|
|
@@ -59,16 +231,23 @@ export function spawnTask(prompt, options = {}) {
|
|
|
59
231
|
providerName,
|
|
60
232
|
modelSpec,
|
|
61
233
|
commandSpec,
|
|
234
|
+
ompSessionOwnership: ompPlan?.provisionalOwnership ?? null,
|
|
62
235
|
});
|
|
63
236
|
|
|
237
|
+
// Row-before-directory: the SQL row is durable proof of an attempted allocation before the
|
|
238
|
+
// partition directory (or anything else OMP-owned) exists on disk. A crash between these two
|
|
239
|
+
// lines leaves a provisional row pointing at a path with nothing there yet — cleanup safely
|
|
240
|
+
// no-ops on a nonexistent path, and normal task-lifecycle recovery handles the row itself.
|
|
64
241
|
addTask(task);
|
|
242
|
+
ompPlan?.createDirectory();
|
|
65
243
|
|
|
66
244
|
const watcherConfig = buildWatcherConfig(
|
|
67
245
|
outputFormat,
|
|
68
246
|
jsonSchema,
|
|
69
247
|
options,
|
|
70
248
|
providerName,
|
|
71
|
-
commandSpec
|
|
249
|
+
commandSpec,
|
|
250
|
+
ompPlan
|
|
72
251
|
);
|
|
73
252
|
const watcherScript = resolveWatcherScript(
|
|
74
253
|
{
|
|
@@ -137,6 +316,7 @@ function buildProviderOptions(options, runtime, modelSelection) {
|
|
|
137
316
|
...(structuredOutputRecovery ? {} : mcpConfigOption(options)),
|
|
138
317
|
...claudeSettingsFileOption(),
|
|
139
318
|
...(!structuredOutputRecovery && options.resume ? { resumeSessionId: options.resume } : {}),
|
|
319
|
+
...(runtime.ompSession ? { ompSession: runtime.ompSession } : {}),
|
|
140
320
|
...(process.env.ZEROSHOT_OPENCODE_AGENT?.trim()
|
|
141
321
|
? { agentName: process.env.ZEROSHOT_OPENCODE_AGENT.trim() }
|
|
142
322
|
: {}),
|
|
@@ -218,6 +398,7 @@ export function buildTaskRecord({
|
|
|
218
398
|
providerName,
|
|
219
399
|
modelSpec,
|
|
220
400
|
commandSpec = {},
|
|
401
|
+
ompSessionOwnership = null,
|
|
221
402
|
}) {
|
|
222
403
|
return {
|
|
223
404
|
id,
|
|
@@ -251,6 +432,7 @@ export function buildTaskRecord({
|
|
|
251
432
|
terminationStrategy: null,
|
|
252
433
|
cancelRequested: false,
|
|
253
434
|
spawnOwnershipToken: process.env[TASK_SPAWN_OWNERSHIP_TOKEN_ENV] || null,
|
|
435
|
+
ompSessionOwnership,
|
|
254
436
|
commandCleanup:
|
|
255
437
|
commandSpec.cleanup?.length > 0
|
|
256
438
|
? {
|
|
@@ -267,8 +449,8 @@ function isRpcStdioLane(providerName) {
|
|
|
267
449
|
|
|
268
450
|
// The returned object is JSON-serialized into the detached watcher's argv, so it must never carry
|
|
269
451
|
// prompt or other task content: `ps` and /proc/<pid>/cmdline expose argv to every local user for
|
|
270
|
-
// the whole lifetime of the watcher.
|
|
271
|
-
function buildWatcherConfig(outputFormat, jsonSchema, options, providerName, commandSpec) {
|
|
452
|
+
// the whole lifetime of the watcher. Partition paths, ids, and digests are not secret.
|
|
453
|
+
function buildWatcherConfig(outputFormat, jsonSchema, options, providerName, commandSpec, ompPlan) {
|
|
272
454
|
return {
|
|
273
455
|
outputFormat,
|
|
274
456
|
jsonSchema,
|
|
@@ -278,6 +460,15 @@ function buildWatcherConfig(outputFormat, jsonSchema, options, providerName, com
|
|
|
278
460
|
command: commandSpec.binary,
|
|
279
461
|
env: commandSpec.env || {},
|
|
280
462
|
commandSpec: buildWatcherCommandSpec(commandSpec, isRpcStdioLane(providerName)),
|
|
463
|
+
...(ompPlan
|
|
464
|
+
? {
|
|
465
|
+
ompSession: ompPlan.session,
|
|
466
|
+
ompResumeExpectation: ompPlan.resumeExpectation,
|
|
467
|
+
// The workspace the ownership row was canonicalized against; the watcher compares it to
|
|
468
|
+
// the session header's own recorded cwd after materialization.
|
|
469
|
+
ompCanonicalWorkspace: ompPlan.provisionalOwnership.canonicalWorkspace,
|
|
470
|
+
}
|
|
471
|
+
: {}),
|
|
281
472
|
};
|
|
282
473
|
}
|
|
283
474
|
|
package/task-lib/store.js
CHANGED
|
@@ -10,9 +10,13 @@ import { join } from 'path';
|
|
|
10
10
|
import Database from 'better-sqlite3';
|
|
11
11
|
import { TASKS_DIR, LOGS_DIR } from './config.js';
|
|
12
12
|
import { generateName } from './name-generator.js';
|
|
13
|
+
import {
|
|
14
|
+
parseOmpSessionOwnership,
|
|
15
|
+
serializeOmpSessionOwnership,
|
|
16
|
+
} from './omp-session-ownership-schema.js';
|
|
13
17
|
|
|
14
18
|
const DB_FILE = join(TASKS_DIR, 'store.db');
|
|
15
|
-
export const TASK_STORE_SCHEMA_VERSION =
|
|
19
|
+
export const TASK_STORE_SCHEMA_VERSION = 5;
|
|
16
20
|
|
|
17
21
|
/** @type {Database.Database | null} */
|
|
18
22
|
let db = null;
|
|
@@ -59,7 +63,8 @@ function getDb() {
|
|
|
59
63
|
termination_strategy TEXT,
|
|
60
64
|
command_cleanup TEXT,
|
|
61
65
|
cancel_requested INTEGER DEFAULT 0,
|
|
62
|
-
spawn_ownership_token TEXT
|
|
66
|
+
spawn_ownership_token TEXT,
|
|
67
|
+
omp_session_ownership TEXT
|
|
63
68
|
);
|
|
64
69
|
|
|
65
70
|
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
@@ -110,6 +115,15 @@ function serializeCommandCleanup(value) {
|
|
|
110
115
|
return value ? JSON.stringify(value) : null;
|
|
111
116
|
}
|
|
112
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Internal accessor for modules that need direct prepared-statement access (SQL compare-and-swap
|
|
120
|
+
* transitions) beyond what the generic load/save/update helpers below offer. See
|
|
121
|
+
* task-lib/omp-session-ownership.js.
|
|
122
|
+
*/
|
|
123
|
+
export function getTaskStoreDatabase() {
|
|
124
|
+
return getDb();
|
|
125
|
+
}
|
|
126
|
+
|
|
113
127
|
export function migrateTaskStore(database) {
|
|
114
128
|
ensureTaskColumn(database, 'process_group_id', 'INTEGER');
|
|
115
129
|
ensureTaskColumn(database, 'termination_strategy', 'TEXT');
|
|
@@ -119,6 +133,9 @@ export function migrateTaskStore(database) {
|
|
|
119
133
|
ensureTaskColumn(database, 'requested_resume_session_id', 'TEXT');
|
|
120
134
|
ensureTaskColumn(database, 'session_id_conflict', 'INTEGER NOT NULL DEFAULT 0');
|
|
121
135
|
ensureTaskColumn(database, 'resume_identity_verified', 'INTEGER NOT NULL DEFAULT 0');
|
|
136
|
+
// No backfill: every pre-v5 row has no OMP session concept, so NULL is exact truth, never a
|
|
137
|
+
// fabricated value. A non-OMP task's resume path is untouched by this column.
|
|
138
|
+
ensureTaskColumn(database, 'omp_session_ownership', 'TEXT');
|
|
122
139
|
database.exec(`
|
|
123
140
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_spawn_ownership_token
|
|
124
141
|
ON tasks(spawn_ownership_token)
|
|
@@ -144,6 +161,9 @@ export function migrateTaskStore(database) {
|
|
|
144
161
|
if (version < 4) {
|
|
145
162
|
database.prepare('UPDATE tasks SET resume_identity_verified = 0').run();
|
|
146
163
|
}
|
|
164
|
+
if (version < 5) {
|
|
165
|
+
// omp_session_ownership already defaults to NULL via ensureTaskColumn above; no backfill.
|
|
166
|
+
}
|
|
147
167
|
database.pragma(`user_version = ${TASK_STORE_SCHEMA_VERSION}`);
|
|
148
168
|
})();
|
|
149
169
|
}
|
|
@@ -192,6 +212,7 @@ function rowToTask(row) {
|
|
|
192
212
|
commandCleanup: parseCommandCleanup(row.command_cleanup),
|
|
193
213
|
cancelRequested: Boolean(row.cancel_requested),
|
|
194
214
|
spawnOwnershipToken: row.spawn_ownership_token,
|
|
215
|
+
ompSessionOwnership: parseOmpSessionOwnership(row.omp_session_ownership),
|
|
195
216
|
};
|
|
196
217
|
}
|
|
197
218
|
|
|
@@ -220,12 +241,12 @@ export function saveTasks(tasks) {
|
|
|
220
241
|
id, prompt, full_prompt, cwd, status, pid, session_id, session_id_conflict, requested_resume_session_id, resume_identity_verified, log_file,
|
|
221
242
|
created_at, updated_at, exit_code, error, provider, model,
|
|
222
243
|
schedule_id, socket_path, attachable, process_group_id, termination_strategy,
|
|
223
|
-
command_cleanup, cancel_requested, spawn_ownership_token
|
|
244
|
+
command_cleanup, cancel_requested, spawn_ownership_token, omp_session_ownership
|
|
224
245
|
) VALUES (
|
|
225
246
|
@id, @prompt, @fullPrompt, @cwd, @status, @pid, @sessionId, @sessionIdConflict, @requestedResumeSessionId, @resumeIdentityVerified, @logFile,
|
|
226
247
|
@createdAt, @updatedAt, @exitCode, @error, @provider, @model,
|
|
227
248
|
@scheduleId, @socketPath, @attachable, @processGroupId, @terminationStrategy,
|
|
228
|
-
@commandCleanup, @cancelRequested, @spawnOwnershipToken
|
|
249
|
+
@commandCleanup, @cancelRequested, @spawnOwnershipToken, @ompSessionOwnership
|
|
229
250
|
)
|
|
230
251
|
`);
|
|
231
252
|
|
|
@@ -260,6 +281,7 @@ export function saveTasks(tasks) {
|
|
|
260
281
|
commandCleanup: serializeCommandCleanup(task.commandCleanup),
|
|
261
282
|
cancelRequested: task.cancelRequested ? 1 : 0,
|
|
262
283
|
spawnOwnershipToken: task.spawnOwnershipToken || null,
|
|
284
|
+
ompSessionOwnership: serializeOmpSessionOwnership(task.ompSessionOwnership || null),
|
|
263
285
|
});
|
|
264
286
|
}
|
|
265
287
|
});
|
|
@@ -350,7 +372,15 @@ export function updateTask(id, updates) {
|
|
|
350
372
|
termination_strategy = @terminationStrategy,
|
|
351
373
|
command_cleanup = @commandCleanup,
|
|
352
374
|
cancel_requested =
|
|
353
|
-
CASE WHEN @hasCancelRequested = 1 THEN @cancelRequested ELSE cancel_requested END
|
|
375
|
+
CASE WHEN @hasCancelRequested = 1 THEN @cancelRequested ELSE cancel_requested END,
|
|
376
|
+
-- Only ever written when the caller explicitly supplies it. This is a read-modify-write
|
|
377
|
+
-- update, so unconditionally rewriting the ownership column would let an unrelated
|
|
378
|
+
-- updateTask (the watcher persisting spawn evidence, say) clobber an owner-fenced
|
|
379
|
+
-- compare-and-swap another process performed in between — see
|
|
380
|
+
-- task-lib/omp-session-ownership.js, whose transitions bypass this statement for exactly
|
|
381
|
+
-- that reason.
|
|
382
|
+
omp_session_ownership =
|
|
383
|
+
CASE WHEN @hasOmpSessionOwnership = 1 THEN @ompSessionOwnership ELSE omp_session_ownership END
|
|
354
384
|
WHERE id = @id
|
|
355
385
|
`
|
|
356
386
|
)
|
|
@@ -379,6 +409,10 @@ export function updateTask(id, updates) {
|
|
|
379
409
|
commandCleanup: serializeCommandCleanup(updated.commandCleanup),
|
|
380
410
|
hasCancelRequested: Object.prototype.hasOwnProperty.call(updates, 'cancelRequested') ? 1 : 0,
|
|
381
411
|
cancelRequested: updated.cancelRequested ? 1 : 0,
|
|
412
|
+
hasOmpSessionOwnership: Object.prototype.hasOwnProperty.call(updates, 'ompSessionOwnership')
|
|
413
|
+
? 1
|
|
414
|
+
: 0,
|
|
415
|
+
ompSessionOwnership: serializeOmpSessionOwnership(updated.ompSessionOwnership || null),
|
|
382
416
|
});
|
|
383
417
|
|
|
384
418
|
return updated;
|
|
@@ -404,12 +438,12 @@ export function addTask(task) {
|
|
|
404
438
|
id, prompt, full_prompt, cwd, status, pid, session_id, session_id_conflict, requested_resume_session_id, resume_identity_verified, log_file,
|
|
405
439
|
created_at, updated_at, exit_code, error, provider, model,
|
|
406
440
|
schedule_id, socket_path, attachable, process_group_id, termination_strategy,
|
|
407
|
-
command_cleanup, cancel_requested, spawn_ownership_token
|
|
441
|
+
command_cleanup, cancel_requested, spawn_ownership_token, omp_session_ownership
|
|
408
442
|
) VALUES (
|
|
409
443
|
@id, @prompt, @fullPrompt, @cwd, @status, @pid, @sessionId, @sessionIdConflict, @requestedResumeSessionId, @resumeIdentityVerified, @logFile,
|
|
410
444
|
@createdAt, @updatedAt, @exitCode, @error, @provider, @model,
|
|
411
445
|
@scheduleId, @socketPath, @attachable, @processGroupId, @terminationStrategy,
|
|
412
|
-
@commandCleanup, @cancelRequested, @spawnOwnershipToken
|
|
446
|
+
@commandCleanup, @cancelRequested, @spawnOwnershipToken, @ompSessionOwnership
|
|
413
447
|
)
|
|
414
448
|
`
|
|
415
449
|
)
|
|
@@ -439,6 +473,7 @@ export function addTask(task) {
|
|
|
439
473
|
commandCleanup: serializeCommandCleanup(fullTask.commandCleanup),
|
|
440
474
|
cancelRequested: fullTask.cancelRequested ? 1 : 0,
|
|
441
475
|
spawnOwnershipToken: fullTask.spawnOwnershipToken || null,
|
|
476
|
+
ompSessionOwnership: serializeOmpSessionOwnership(fullTask.ompSessionOwnership || null),
|
|
442
477
|
});
|
|
443
478
|
|
|
444
479
|
return fullTask;
|