@playcraft/cli 0.1.47-beta → 0.1.49-beta
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/dist/commands/tools-batch.js +12 -2
- package/dist/workspace-runtime/batch/control.js +6 -0
- package/dist/workspace-runtime/batch/deliver.js +105 -87
- package/dist/workspace-runtime/batch/generate.js +26 -3
- package/dist/workspace-runtime/batch/prepare.js +169 -56
- package/dist/workspace-runtime/batch/source-capture.js +7 -20
- package/dist/workspace-runtime/batch/source-publish.js +2 -4
- package/dist/workspace-runtime/codex/app-server.js +29 -4
- package/dist/workspace-runtime/codex/delivery-tools.js +144 -0
- package/dist/workspace-runtime/codex/tool-response.js +10 -0
- package/dist/workspace-runtime/codex/ui-tools.js +22 -5
- package/dist/workspace-runtime/previews/diagnostics.js +44 -0
- package/dist/workspace-runtime/previews/publish.js +16 -4
- package/dist/workspace-runtime/serve.js +60 -0
- package/dist/workspace-runtime/server/accept-operation.js +61 -0
- package/dist/workspace-runtime/server/dispatch.js +47 -2
- package/dist/workspace-runtime/server/http.js +163 -130
- package/dist/workspace-runtime/server/operation-record.js +33 -0
- package/dist/workspace-runtime/server/submit-operation.js +41 -0
- package/dist/workspace-runtime/workspaces/append-only-log.js +62 -0
- package/dist/workspace-runtime/workspaces/delivery-build.js +331 -0
- package/dist/workspace-runtime/workspaces/delivery-store.js +168 -0
- package/dist/workspace-runtime/workspaces/delivery.js +64 -0
- package/dist/workspace-runtime/workspaces/dependencies.js +15 -0
- package/dist/workspace-runtime/workspaces/developer-command.js +35 -1
- package/dist/workspace-runtime/workspaces/directories.js +9 -4
- package/dist/workspace-runtime/workspaces/operation-log.js +23 -0
- package/package.json +3 -3
|
@@ -35,12 +35,22 @@ const messages = {
|
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
const language = () => /^zh/i.test(process.env.PLAYCRAFT_LANG ?? process.env.LANG ?? '') ? messages.zh : messages.en;
|
|
38
|
+
const modelConfigIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
39
|
+
/** Model bound by the runtime to the conversation that is creating the batch. */
|
|
40
|
+
export function sessionModelConfigId(env = process.env) {
|
|
41
|
+
const value = env.PLAYCRAFT_MODEL_CONFIG_ID?.trim();
|
|
42
|
+
return value && modelConfigIdPattern.test(value) ? value : undefined;
|
|
43
|
+
}
|
|
44
|
+
function bindSessionModel(body) {
|
|
45
|
+
const modelConfigId = sessionModelConfigId();
|
|
46
|
+
return modelConfigId ? { ...body, modelConfigId } : body;
|
|
47
|
+
}
|
|
38
48
|
export async function createBatchDraft(path, client = new AgentApiClient()) {
|
|
39
|
-
const draft = batchCreateSchema.parse(JSON.parse(await readFile(path, 'utf8')));
|
|
49
|
+
const draft = batchCreateSchema.parse(bindSessionModel(JSON.parse(await readFile(path, 'utf8'))));
|
|
40
50
|
return client.post('/create-batch', draft);
|
|
41
51
|
}
|
|
42
52
|
export async function createPromptBatch(input, client = new AgentApiClient()) {
|
|
43
|
-
return client.post('/create-batch', batchPromptCreateSchema.parse(input));
|
|
53
|
+
return client.post('/create-batch', batchPromptCreateSchema.parse(bindSessionModel(input)));
|
|
44
54
|
}
|
|
45
55
|
export function registerBatchCommands(tools) {
|
|
46
56
|
const t = language();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
2
|
import { sandboxBatchMemberStopSchema } from '@playcraft/common/cloud-workspace/runtime';
|
|
3
3
|
import { groupExited, stopProcessGroup } from './processes.js';
|
|
4
|
+
import { settleObservedGenerate } from './generate.js';
|
|
4
5
|
import { memberStatus, readMember, serializeRun, writeMember, writeStopMarker } from './records.js';
|
|
5
6
|
async function prove(record) {
|
|
6
7
|
let stopped = true;
|
|
@@ -26,6 +27,11 @@ export async function readBatchMemberStatus(state, runId) {
|
|
|
26
27
|
const record = await readMember(state, runId);
|
|
27
28
|
if (!record)
|
|
28
29
|
return undefined;
|
|
30
|
+
if (record.phase === 'generate' && record.state === 'running') {
|
|
31
|
+
const settled = await settleObservedGenerate(state, record);
|
|
32
|
+
if (settled.state !== 'running')
|
|
33
|
+
return memberStatus(settled);
|
|
34
|
+
}
|
|
29
35
|
const processes = record.processes.map(process => ({ ...process, stopped: process.stopped || groupExited(process.pid) }));
|
|
30
36
|
const live = processes.some(process => !process.stopped);
|
|
31
37
|
let next = { ...record, processes };
|
|
@@ -1,26 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { lstat,
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { hashOperationPayload, sandboxBatchMemberDeliverSchema } from '@playcraft/common/cloud-workspace/runtime';
|
|
5
|
-
import { findExternalLocalReferences } from '../previews/single-file.js';
|
|
4
|
+
import { hashOperationPayload, sandboxBatchMemberDeliverSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
6
5
|
import { workspaceRoot } from '../workspaces/registry.js';
|
|
7
|
-
import { requireBatchContentChanges } from './content-changes.js';
|
|
8
|
-
import { gitRead, gitWrite } from './git.js';
|
|
9
6
|
import { groupExited, runProcessGroup, stopProcessGroup } from './processes.js';
|
|
10
|
-
import {
|
|
7
|
+
import { memberStatus, readMember, readStopMarker, serializeRun, writeMember, } from './records.js';
|
|
8
|
+
/** Base build writes dist/index.html. Channel packaging stays on export playable (`--mode full`). */
|
|
11
9
|
function buildCommand() {
|
|
12
|
-
return process.env.PLAYCRAFT_BATCH_BUILD_COMMAND
|
|
13
|
-
?? 'playcraft build --mode full --platform playcraft --format html --output dist/index.html .';
|
|
10
|
+
return (process.env.PLAYCRAFT_BATCH_BUILD_COMMAND ?? 'CI=1 playcraft build --mode base --output dist .');
|
|
14
11
|
}
|
|
15
|
-
async function
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
throw new Error('
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
async function readBuiltHtml(root) {
|
|
13
|
+
const htmlPath = join(root, 'dist', 'index.html');
|
|
14
|
+
const info = await lstat(htmlPath).catch(() => undefined);
|
|
15
|
+
if (!info?.isFile() || info.isSymbolicLink())
|
|
16
|
+
throw new Error('batch_build_failed');
|
|
17
|
+
const handle = await open(htmlPath, constants.O_RDONLY | constants.O_NOFOLLOW).catch(() => {
|
|
18
|
+
throw new Error('batch_build_failed');
|
|
19
|
+
});
|
|
20
|
+
await handle.close();
|
|
24
21
|
}
|
|
25
22
|
async function prove(record) {
|
|
26
23
|
let stopped = true;
|
|
@@ -35,8 +32,11 @@ async function prove(record) {
|
|
|
35
32
|
}
|
|
36
33
|
return stopped;
|
|
37
34
|
}
|
|
38
|
-
export async function deliverBatchMember(state, body
|
|
39
|
-
if (!body ||
|
|
35
|
+
export async function deliverBatchMember(state, body) {
|
|
36
|
+
if (!body ||
|
|
37
|
+
typeof body !== 'object' ||
|
|
38
|
+
!('occupancyId' in body) ||
|
|
39
|
+
!body.occupancyId)
|
|
40
40
|
throw new Error('occupancy_required');
|
|
41
41
|
const parsed = sandboxBatchMemberDeliverSchema.safeParse(body);
|
|
42
42
|
if (!parsed.success)
|
|
@@ -47,109 +47,127 @@ export async function deliverBatchMember(state, body, options) {
|
|
|
47
47
|
const existing = await readMember(state, command.runId);
|
|
48
48
|
if (existing && command.ownerFence < existing.ownerFence)
|
|
49
49
|
throw new Error('stale_owner');
|
|
50
|
-
if (existing?.state === 'completed' &&
|
|
50
|
+
if (existing?.state === 'completed' &&
|
|
51
|
+
existing.phase === 'deliver' &&
|
|
52
|
+
existing.evidence &&
|
|
53
|
+
existing.commandHash?.deliver === hash &&
|
|
54
|
+
existing.ownerFence === command.ownerFence) {
|
|
51
55
|
return memberStatus(existing);
|
|
52
56
|
}
|
|
53
|
-
if (await readStopMarker(state, command.runId) &&
|
|
57
|
+
if ((await readStopMarker(state, command.runId)) &&
|
|
58
|
+
existing?.state === 'completed' &&
|
|
59
|
+
existing.evidence)
|
|
54
60
|
return memberStatus(existing);
|
|
55
61
|
if (await readStopMarker(state, command.runId))
|
|
56
62
|
return memberStatus(await markStopped(state, command, existing));
|
|
57
|
-
if (existing &&
|
|
63
|
+
if (existing &&
|
|
64
|
+
existing.ownerFence === command.ownerFence &&
|
|
65
|
+
existing.commandHash?.deliver &&
|
|
66
|
+
existing.commandHash.deliver !== hash &&
|
|
67
|
+
existing.state === 'completed') {
|
|
58
68
|
throw new Error('batch_command_conflict');
|
|
59
69
|
}
|
|
60
70
|
const root = await workspaceRoot(state, command.workspaceId, command.directoryName);
|
|
61
71
|
let record = await writeMember(state, {
|
|
62
|
-
runId: command.runId,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
runId: command.runId,
|
|
73
|
+
ownerFence: command.ownerFence,
|
|
74
|
+
occupancyId: command.occupancyId,
|
|
75
|
+
workspaceId: command.workspaceId,
|
|
76
|
+
directoryName: command.directoryName,
|
|
77
|
+
phase: 'deliver',
|
|
78
|
+
state: 'running',
|
|
79
|
+
writersStopped: false,
|
|
80
|
+
holding: true,
|
|
81
|
+
stopRequested: false,
|
|
82
|
+
processes: existing?.processes ?? [],
|
|
83
|
+
sessionId: existing?.sessionId,
|
|
84
|
+
operationId: existing?.operationId,
|
|
85
|
+
commandHash: { ...existing?.commandHash, deliver: hash },
|
|
86
|
+
snapshotDigest: existing?.snapshotDigest,
|
|
87
|
+
branchName: existing?.branchName,
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
sourceCommit: existing?.sourceCommit ??
|
|
90
|
+
state.journal
|
|
91
|
+
.getWorkspace(command.workspaceId)
|
|
92
|
+
?.directories?.find((directory) => directory.directoryName === command.directoryName)
|
|
93
|
+
?.sourceCommit,
|
|
67
94
|
beforeTree: existing?.beforeTree,
|
|
68
95
|
});
|
|
69
|
-
const buildRoot = join(state.config.stateDir, 'batch-builds', `${command.runId}-${randomUUID()}`);
|
|
70
96
|
try {
|
|
71
|
-
const savedCommit = command.fromCommit ?? await saveExact(root);
|
|
72
|
-
if (command.fromCommit)
|
|
73
|
-
await gitRead(root, ['cat-file', '-e', `${command.fromCommit}^{commit}`]);
|
|
74
|
-
await requireBatchContentChanges(root, record.sourceCommit ?? '', savedCommit);
|
|
75
97
|
if (await readStopMarker(state, command.runId))
|
|
76
98
|
return memberStatus(await markStopped(state, command, record));
|
|
77
|
-
|
|
78
|
-
if (command.pushRemote) {
|
|
79
|
-
const branch = existing?.branchName ?? (await gitRead(root, ['symbolic-ref', '--short', 'HEAD'])).trim();
|
|
80
|
-
try {
|
|
81
|
-
await gitWrite(root, ['push', '--porcelain', 'origin', `${savedCommit}:refs/heads/${branch}`]);
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
throw new Error('batch_push_failed');
|
|
85
|
-
}
|
|
86
|
-
const remote = (await gitWrite(root, ['ls-remote', 'origin', `refs/heads/${branch}`])).slice(0, savedCommit.length);
|
|
87
|
-
if (remote !== savedCommit)
|
|
88
|
-
throw new Error('batch_push_failed');
|
|
89
|
-
pushedCommit = savedCommit;
|
|
90
|
-
}
|
|
91
|
-
await mkdir(join(buildRoot, '..'), { recursive: true });
|
|
92
|
-
await gitWrite(join(buildRoot, '..'), ['clone', '--local', '--no-hardlinks', '--no-checkout', '--', root, buildRoot]);
|
|
93
|
-
await gitWrite(buildRoot, ['checkout', '--detach', savedCommit]);
|
|
94
|
-
const built = await runProcessGroup(buildRoot, buildCommand(), 'build', async (process) => {
|
|
99
|
+
const built = await runProcessGroup(root, buildCommand(), 'build', async (process) => {
|
|
95
100
|
record = await writeMember(state, { ...record, processes: [...record.processes, process] });
|
|
96
101
|
});
|
|
97
|
-
record = await writeMember(state, {
|
|
102
|
+
record = await writeMember(state, {
|
|
103
|
+
...record,
|
|
104
|
+
processes: record.processes.map((process) => process.pid === built.process.pid ? built.process : process),
|
|
105
|
+
});
|
|
98
106
|
if (await readStopMarker(state, command.runId))
|
|
99
107
|
return memberStatus(await markStopped(state, command, record));
|
|
100
108
|
if (built.code !== 0)
|
|
101
109
|
throw new Error('batch_build_failed');
|
|
102
|
-
|
|
103
|
-
if (!(await lstat(htmlPath).catch(() => undefined))?.isFile())
|
|
104
|
-
throw new Error('batch_build_failed');
|
|
105
|
-
const entries = await readdir(join(buildRoot, 'dist'));
|
|
106
|
-
if (!entries.includes('index.html'))
|
|
107
|
-
throw new Error('batch_build_failed');
|
|
108
|
-
const bodyBytes = await readFile(htmlPath);
|
|
109
|
-
if (findExternalLocalReferences(bodyBytes.toString()).length)
|
|
110
|
-
throw new Error('batch_verify_failed');
|
|
111
|
-
const buildDigest = createHash('sha256').update(bodyBytes).digest('hex');
|
|
112
|
-
const previewRef = `workspaces/${command.workspaceId}/batches/members/${command.runId}/${buildDigest}.html`;
|
|
113
|
-
const store = options?.objectStore ?? filesystemObjectStore(state);
|
|
114
|
-
await store.put(previewRef, bodyBytes);
|
|
115
|
-
const published = await store.get(previewRef);
|
|
116
|
-
if (createHash('sha256').update(published).digest('hex') !== buildDigest)
|
|
117
|
-
throw new Error('batch_verify_failed');
|
|
118
|
-
await mkdir(join(root, 'dist'), { recursive: true });
|
|
119
|
-
await writeFile(join(root, 'dist', 'index.html'), published);
|
|
120
|
-
if (createHash('sha256').update(await readFile(join(root, 'dist', 'index.html'))).digest('hex') !== buildDigest)
|
|
121
|
-
throw new Error('batch_verify_failed');
|
|
110
|
+
await readBuiltHtml(root);
|
|
122
111
|
const writersStopped = await prove(record);
|
|
123
112
|
if (!writersStopped)
|
|
124
|
-
return memberStatus(await writeMember(state, {
|
|
113
|
+
return memberStatus(await writeMember(state, {
|
|
114
|
+
...record,
|
|
115
|
+
state: 'stopping',
|
|
116
|
+
writersStopped: false,
|
|
117
|
+
holding: true,
|
|
118
|
+
}));
|
|
125
119
|
if (await readStopMarker(state, command.runId))
|
|
126
|
-
return memberStatus(await markStopped(state, command,
|
|
120
|
+
return memberStatus(await markStopped(state, command, record));
|
|
127
121
|
record = await writeMember(state, {
|
|
128
|
-
...record,
|
|
129
|
-
|
|
122
|
+
...record,
|
|
123
|
+
state: 'completed',
|
|
124
|
+
writersStopped: true,
|
|
125
|
+
holding: false,
|
|
126
|
+
error: undefined,
|
|
127
|
+
evidence: { verified: true },
|
|
130
128
|
});
|
|
131
129
|
return memberStatus(record);
|
|
132
130
|
}
|
|
133
131
|
catch (error) {
|
|
134
|
-
const code = error instanceof Error && /^[a-z0-9_]+$/.test(error.message)
|
|
132
|
+
const code = error instanceof Error && /^[a-z0-9_]+$/.test(error.message)
|
|
133
|
+
? error.message
|
|
134
|
+
: 'batch_deliver_failed';
|
|
135
135
|
const writersStopped = await prove(record);
|
|
136
|
-
record = await writeMember(state, {
|
|
136
|
+
record = await writeMember(state, {
|
|
137
|
+
...record,
|
|
138
|
+
state: writersStopped ? 'failed' : 'stopping',
|
|
139
|
+
writersStopped,
|
|
140
|
+
holding: !writersStopped,
|
|
141
|
+
error: code,
|
|
142
|
+
});
|
|
137
143
|
return memberStatus(record);
|
|
138
144
|
}
|
|
139
|
-
finally {
|
|
140
|
-
await rm(buildRoot, { recursive: true, force: true });
|
|
141
|
-
}
|
|
142
145
|
});
|
|
143
146
|
}
|
|
144
147
|
async function markStopped(state, command, existing) {
|
|
145
148
|
if (existing)
|
|
146
149
|
await prove(existing);
|
|
147
150
|
return writeMember(state, {
|
|
148
|
-
runId: command.runId,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
runId: command.runId,
|
|
152
|
+
ownerFence: command.ownerFence,
|
|
153
|
+
occupancyId: existing?.occupancyId ?? command.occupancyId,
|
|
154
|
+
workspaceId: command.workspaceId,
|
|
155
|
+
directoryName: command.directoryName,
|
|
156
|
+
phase: 'deliver',
|
|
157
|
+
state: 'stopped',
|
|
158
|
+
writersStopped: true,
|
|
159
|
+
holding: false,
|
|
160
|
+
stopRequested: true,
|
|
161
|
+
processes: existing?.processes ?? [],
|
|
162
|
+
sessionId: existing?.sessionId,
|
|
163
|
+
operationId: existing?.operationId,
|
|
164
|
+
headCommit: existing?.headCommit,
|
|
165
|
+
evidence: existing?.evidence,
|
|
166
|
+
commandHash: existing?.commandHash,
|
|
167
|
+
snapshotDigest: existing?.snapshotDigest,
|
|
168
|
+
branchName: existing?.branchName,
|
|
169
|
+
updatedAt: new Date().toISOString(),
|
|
170
|
+
sourceCommit: existing?.sourceCommit,
|
|
171
|
+
beforeTree: existing?.beforeTree,
|
|
154
172
|
});
|
|
155
173
|
}
|
|
@@ -80,7 +80,8 @@ export async function generateBatchMember(state, body) {
|
|
|
80
80
|
await mkdir(tempDir, { recursive: true, mode: 0o700 });
|
|
81
81
|
const policy = memberExecutionPolicy(root, tempDir);
|
|
82
82
|
await bindMemberPolicy(state, sessionId, policy);
|
|
83
|
-
const
|
|
83
|
+
const modelConfigId = command.model && /^[0-9a-f-]{36}$/i.test(command.model) ? command.model : undefined;
|
|
84
|
+
const turnPayload = { text: command.prompt, permissionMode: 'auto', ...(modelConfigId ? { modelConfigId } : {}) };
|
|
84
85
|
const turnKey = operation(state, { kind: 'session.turn', clientRequestId: `batch-turn:${command.runId}`, sessionId, workspaceId: command.workspaceId, directoryName: command.directoryName, payload: turnPayload });
|
|
85
86
|
const prior = state.journal.getOperation(turnKey.scopeKey);
|
|
86
87
|
if (prior && prior.payloadHash !== turnKey.record.payloadHash)
|
|
@@ -114,9 +115,9 @@ export async function generateBatchMember(state, body) {
|
|
|
114
115
|
});
|
|
115
116
|
try {
|
|
116
117
|
if (!state.journal.getSession(sessionId)) {
|
|
117
|
-
const created = operation(state, { kind: 'session.create', clientRequestId: `batch-create:${command.runId}`, sessionId, workspaceId: command.workspaceId, directoryName: command.directoryName, payload: { permissionMode: 'auto' } });
|
|
118
|
+
const created = operation(state, { kind: 'session.create', clientRequestId: `batch-create:${command.runId}`, sessionId, workspaceId: command.workspaceId, directoryName: command.directoryName, payload: { permissionMode: 'auto', ...(modelConfigId ? { modelConfigId } : {}) } });
|
|
118
119
|
if (!state.journal.getOperation(created.scopeKey))
|
|
119
|
-
await executeOperation(state, created.scopeKey, created.record, { permissionMode: 'auto' });
|
|
120
|
+
await executeOperation(state, created.scopeKey, created.record, { permissionMode: 'auto', ...(modelConfigId ? { modelConfigId } : {}) });
|
|
120
121
|
}
|
|
121
122
|
if (await readStopMarker(state, command.runId))
|
|
122
123
|
return memberStatus(await stoppedRecord(state, command, record));
|
|
@@ -179,6 +180,11 @@ async function finishGenerate(state, record, started, root, early) {
|
|
|
179
180
|
return stoppedRecord(state, record, record);
|
|
180
181
|
return completeGeneration(state, { ...record, operationId: started.operationId }, root);
|
|
181
182
|
}
|
|
183
|
+
if (!terminal) {
|
|
184
|
+
return writeMember(state, {
|
|
185
|
+
...record, state: 'running', writersStopped: false, holding: true, operationId: started.operationId, error: undefined,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
182
188
|
const writersStopped = await proveStopped(record);
|
|
183
189
|
return writeMember(state, {
|
|
184
190
|
...record, state: writersStopped ? (terminal === 'interrupted' ? 'stopped' : 'failed') : 'unknown',
|
|
@@ -186,6 +192,23 @@ async function finishGenerate(state, record, started, root, early) {
|
|
|
186
192
|
error: writersStopped ? (terminal === 'interrupted' ? undefined : 'batch_turn_failed') : 'native_receipt_lost',
|
|
187
193
|
});
|
|
188
194
|
}
|
|
195
|
+
/** A later status read finishes a generate turn that was still running when dispatch returned. */
|
|
196
|
+
export async function settleObservedGenerate(state, record) {
|
|
197
|
+
if (record.phase !== 'generate' || record.state !== 'running' || record.stopRequested)
|
|
198
|
+
return record;
|
|
199
|
+
const session = record.sessionId ? state.journal.getSession(record.sessionId) : undefined;
|
|
200
|
+
if (!session || session.turnBlock === 'uncertain' || session.turnStatus === 'starting' || session.turnStatus === 'running')
|
|
201
|
+
return record;
|
|
202
|
+
const terminal = session.turnStatus === 'completed' ? 'completed'
|
|
203
|
+
: session.turnStatus === 'failed' ? 'failed'
|
|
204
|
+
: session.turnStatus === 'interrupted' ? 'interrupted'
|
|
205
|
+
: session.turnStatus === 'waiting_for_user' ? 'needs_user'
|
|
206
|
+
: undefined;
|
|
207
|
+
if (!terminal)
|
|
208
|
+
return record;
|
|
209
|
+
const root = await workspaceRoot(state, record.workspaceId, record.directoryName);
|
|
210
|
+
return finishGenerate(state, record, { operationId: record.operationId, status: 'accepted' }, root, () => terminal);
|
|
211
|
+
}
|
|
189
212
|
async function reconcileGenerate(state, record) {
|
|
190
213
|
const session = record.sessionId ? state.journal.getSession(record.sessionId) : undefined;
|
|
191
214
|
if (record.state === 'unknown' && session?.turnStatus === 'completed') {
|