@zq-silk/yui 2.3.0 → 2.4.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/README.md +15 -1
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +17 -8
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +25 -2
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +25 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +7 -12
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +48 -71
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +2 -1
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +295 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +106 -8
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/release-workflow.md +28 -0
- package/docs/release-workflow.zh-CN.md +21 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/dist/web/assets/client/app.js +0 -808
- package/dist/web/assets/client/components.js +0 -477
- package/dist/web/assets/client/detail.js +0 -379
- package/dist/web/assets/client/dock.js +0 -309
- package/dist/web/assets/client/evidence.js +0 -364
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1277
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -198
- package/dist/web/assets/client/sections.js +0 -349
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -216
- package/dist/web/assets/styles/views.js +0 -309
- /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
|
@@ -63,13 +63,17 @@ export function assertProjectActive(project, action) {
|
|
|
63
63
|
throw new Error(`Project is retired and cannot ${action}: ${project.id}.`);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
export function addProjectKnowledge(project, id, title, body, now, provenance) {
|
|
66
|
+
export function addProjectKnowledge(project, id, title, body, now, provenance, applicability = {}) {
|
|
67
67
|
const timestamp = now.toISOString();
|
|
68
68
|
const knowledge = {
|
|
69
|
-
schemaVersion:
|
|
69
|
+
schemaVersion: 2,
|
|
70
|
+
version: 1,
|
|
71
|
+
history: [],
|
|
70
72
|
id: requireIdentity(id, "Project knowledge id"),
|
|
71
73
|
title: requireText(title, "Project knowledge title"),
|
|
72
74
|
body: requireText(body, "Project knowledge body"),
|
|
75
|
+
...(applicability.scope === undefined ? {} : { scope: requireText(applicability.scope, "Knowledge scope") }),
|
|
76
|
+
...(applicability.expiresWhen === undefined ? {} : { expiresWhen: requireText(applicability.expiresWhen, "Knowledge expiry condition") }),
|
|
73
77
|
status: "active",
|
|
74
78
|
...(provenance === undefined ? {} : { provenance: validateKnowledgeProvenance(provenance) }),
|
|
75
79
|
createdAt: timestamp,
|
|
@@ -114,19 +118,34 @@ export function updateProjectKnowledge(project, id, patch, now) {
|
|
|
114
118
|
if (entry.status === "retired") {
|
|
115
119
|
throw new Error(`Project knowledge is retired: ${knowledgeId}.`);
|
|
116
120
|
}
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
const { scope: _scope, expiresWhen: _expiresWhen, ...base } = entry;
|
|
122
|
+
const scope = patch.scope === undefined ? entry.scope
|
|
123
|
+
: patch.scope === null ? undefined : requireText(patch.scope, "Knowledge scope");
|
|
124
|
+
const expiresWhen = patch.expiresWhen === undefined ? entry.expiresWhen
|
|
125
|
+
: patch.expiresWhen === null ? undefined : requireText(patch.expiresWhen, "Knowledge expiry condition");
|
|
126
|
+
const next = {
|
|
127
|
+
...base,
|
|
128
|
+
...(scope === undefined ? {} : { scope }),
|
|
129
|
+
...(expiresWhen === undefined ? {} : { expiresWhen }),
|
|
119
130
|
title: patch.title === undefined
|
|
120
131
|
? entry.title
|
|
121
132
|
: requireText(patch.title, "Project knowledge title"),
|
|
122
133
|
body: patch.body === undefined
|
|
123
134
|
? entry.body
|
|
124
135
|
: requireText(patch.body, "Project knowledge body"),
|
|
136
|
+
...(patch.provenance === undefined ? {} : { provenance: validateKnowledgeProvenance(patch.provenance) }),
|
|
125
137
|
updatedAt: now.toISOString()
|
|
126
138
|
};
|
|
139
|
+
if (next.title === entry.title && next.body === entry.body
|
|
140
|
+
&& next.scope === entry.scope && next.expiresWhen === entry.expiresWhen
|
|
141
|
+
&& JSON.stringify(next.provenance) === JSON.stringify(entry.provenance))
|
|
142
|
+
return entry;
|
|
143
|
+
return { ...next, version: entry.version + 1, history: [...entry.history, knowledgeRevision(entry)] };
|
|
127
144
|
});
|
|
128
145
|
if (!found)
|
|
129
146
|
throw new Error(`Project knowledge not found: ${knowledgeId}.`);
|
|
147
|
+
if (knowledge.every((entry, index) => entry === project.knowledge[index]))
|
|
148
|
+
return project;
|
|
130
149
|
return validateProject({ ...project, knowledge, updatedAt: now.toISOString() });
|
|
131
150
|
}
|
|
132
151
|
export function retireProjectKnowledge(project, id, now) {
|
|
@@ -136,31 +155,42 @@ export function retireProjectKnowledge(project, id, now) {
|
|
|
136
155
|
if (entry.id !== knowledgeId)
|
|
137
156
|
return entry;
|
|
138
157
|
found = true;
|
|
158
|
+
if (entry.status === "retired")
|
|
159
|
+
return entry;
|
|
139
160
|
return {
|
|
140
161
|
...entry,
|
|
162
|
+
version: entry.version + 1,
|
|
163
|
+
history: [...entry.history, knowledgeRevision(entry)],
|
|
141
164
|
status: "retired",
|
|
142
165
|
updatedAt: now.toISOString()
|
|
143
166
|
};
|
|
144
167
|
});
|
|
145
168
|
if (!found)
|
|
146
169
|
throw new Error(`Project knowledge not found: ${knowledgeId}.`);
|
|
170
|
+
if (knowledge.every((entry, index) => entry === project.knowledge[index]))
|
|
171
|
+
return project;
|
|
147
172
|
return validateProject({ ...project, knowledge, updatedAt: now.toISOString() });
|
|
148
173
|
}
|
|
174
|
+
function knowledgeRevision(entry) {
|
|
175
|
+
const { schemaVersion: _schema, id: _id, history: _history, createdAt: _created, ...revision } = entry;
|
|
176
|
+
return revision;
|
|
177
|
+
}
|
|
149
178
|
/**
|
|
150
179
|
* Stable dedup fingerprint for a promotion candidate. Two proposals with the
|
|
151
|
-
* same source
|
|
152
|
-
*
|
|
180
|
+
* same source, content, applicability and replacement intent are identical.
|
|
181
|
+
* Trim outer whitespace only; preserve case, internal whitespace and Unicode.
|
|
182
|
+
* A JSON tuple avoids collisions between user-supplied identity delimiters.
|
|
153
183
|
*/
|
|
154
184
|
export function knowledgeProposalFingerprint(input) {
|
|
155
|
-
const
|
|
156
|
-
input.projectId,
|
|
157
|
-
input.source.taskId,
|
|
158
|
-
input.source.decisionId ?? "",
|
|
159
|
-
input.source.milestoneId ?? "",
|
|
160
|
-
input.source.commitSha ?? ""
|
|
161
|
-
].join("|");
|
|
185
|
+
const optional = (value) => value === undefined ? null : requireText(value, "Knowledge fingerprint field");
|
|
162
186
|
return createHash("sha256")
|
|
163
|
-
.update(
|
|
187
|
+
.update(JSON.stringify([
|
|
188
|
+
"knowledge-proposal-v2", requireText(input.projectId, "Project id"),
|
|
189
|
+
requireText(input.source.taskId, "Source Task"), optional(input.source.decisionId),
|
|
190
|
+
optional(input.source.milestoneId), optional(input.source.commitSha),
|
|
191
|
+
requireText(input.title, "Knowledge title"), requireText(input.body, "Knowledge body"),
|
|
192
|
+
optional(input.scope), optional(input.expiresWhen), optional(input.supersedesKnowledgeId)
|
|
193
|
+
]))
|
|
164
194
|
.digest("hex");
|
|
165
195
|
}
|
|
166
196
|
/** Digest of the cited source evidence, so a later Agent can verify it. */
|
|
@@ -334,17 +364,18 @@ export function validateProject(project) {
|
|
|
334
364
|
requireGitRef(project.developmentBranch, "Project development branch");
|
|
335
365
|
const knowledgeIds = new Set();
|
|
336
366
|
for (const entry of project.knowledge) {
|
|
337
|
-
if (entry.schemaVersion !==
|
|
338
|
-
throw new Error("Project knowledge must use schemaVersion
|
|
367
|
+
if (entry.schemaVersion !== 2)
|
|
368
|
+
throw new Error("Project knowledge must use schemaVersion 2.");
|
|
339
369
|
requireIdentity(entry.id, "Project knowledge id");
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
throw new Error("Project knowledge status is invalid.");
|
|
344
|
-
}
|
|
345
|
-
if (entry.provenance !== undefined) {
|
|
346
|
-
validateKnowledgeProvenance(entry.provenance);
|
|
370
|
+
validateKnowledgeRevision(entry);
|
|
371
|
+
if (!Array.isArray(entry.history) || entry.history.length !== entry.version - 1) {
|
|
372
|
+
throw new Error("Project knowledge history must contain every prior version.");
|
|
347
373
|
}
|
|
374
|
+
entry.history.forEach((revision, index) => {
|
|
375
|
+
validateKnowledgeRevision(revision);
|
|
376
|
+
if (revision.version !== index + 1)
|
|
377
|
+
throw new Error("Project knowledge history is out of order.");
|
|
378
|
+
});
|
|
348
379
|
requireTimestamp(entry.createdAt, "Project knowledge createdAt");
|
|
349
380
|
requireTimestamp(entry.updatedAt, "Project knowledge updatedAt");
|
|
350
381
|
if (knowledgeIds.has(entry.id))
|
|
@@ -363,6 +394,21 @@ export function validateProject(project) {
|
|
|
363
394
|
requireTimestamp(project.updatedAt, "Project updatedAt");
|
|
364
395
|
return project;
|
|
365
396
|
}
|
|
397
|
+
function validateKnowledgeRevision(entry) {
|
|
398
|
+
if (!Number.isSafeInteger(entry.version) || entry.version < 1)
|
|
399
|
+
throw new Error("Invalid knowledge version.");
|
|
400
|
+
requireText(entry.title, "Project knowledge title");
|
|
401
|
+
requireText(entry.body, "Project knowledge body");
|
|
402
|
+
if (entry.scope !== undefined)
|
|
403
|
+
requireText(entry.scope, "Knowledge scope");
|
|
404
|
+
if (entry.expiresWhen !== undefined)
|
|
405
|
+
requireText(entry.expiresWhen, "Knowledge expiry condition");
|
|
406
|
+
if (!["active", "retired"].includes(entry.status))
|
|
407
|
+
throw new Error("Project knowledge status is invalid.");
|
|
408
|
+
if (entry.provenance !== undefined)
|
|
409
|
+
validateKnowledgeProvenance(entry.provenance);
|
|
410
|
+
requireTimestamp(entry.updatedAt, "Project knowledge updatedAt");
|
|
411
|
+
}
|
|
366
412
|
function validateKnowledgeProvenance(provenance) {
|
|
367
413
|
requireIdentity(provenance.taskId, "Knowledge provenance task id");
|
|
368
414
|
if (provenance.decisionId !== undefined) {
|
|
@@ -394,8 +440,8 @@ function validateKnowledgeProvenance(provenance) {
|
|
|
394
440
|
return provenance;
|
|
395
441
|
}
|
|
396
442
|
function validateKnowledgeProposal(proposal, projectId) {
|
|
397
|
-
if (proposal.schemaVersion !==
|
|
398
|
-
throw new Error("Knowledge proposal must use schemaVersion
|
|
443
|
+
if (proposal.schemaVersion !== 2) {
|
|
444
|
+
throw new Error("Knowledge proposal must use schemaVersion 2.");
|
|
399
445
|
}
|
|
400
446
|
requireIdentity(proposal.id, "Knowledge proposal id");
|
|
401
447
|
if (proposal.projectId !== projectId) {
|
|
@@ -403,6 +449,9 @@ function validateKnowledgeProposal(proposal, projectId) {
|
|
|
403
449
|
}
|
|
404
450
|
requireText(proposal.title, "Knowledge proposal title");
|
|
405
451
|
requireText(proposal.body, "Knowledge proposal body");
|
|
452
|
+
if (proposal.fingerprint !== knowledgeProposalFingerprint(proposal)) {
|
|
453
|
+
throw new Error("Knowledge proposal fingerprint does not match its content.");
|
|
454
|
+
}
|
|
406
455
|
if (!["pending", "accepted", "rejected"].includes(proposal.status)) {
|
|
407
456
|
throw new Error("Knowledge proposal status is invalid.");
|
|
408
457
|
}
|
|
@@ -10,6 +10,7 @@ import { execFile } from "node:child_process";
|
|
|
10
10
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
11
11
|
import { basename, join, resolve } from "node:path";
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
|
+
import { readGitWorktrees } from "../repository/gitWorktreeInventory.js";
|
|
13
14
|
import { managedProjectPath } from "../repository/project.js";
|
|
14
15
|
import { managedRuntimeRoot } from "../storage/homeLayout.js";
|
|
15
16
|
import { isResourceQuarantinePath } from "./resourceRegistry.js";
|
|
@@ -54,8 +55,7 @@ export function parseGitWorktreePorcelain(output) {
|
|
|
54
55
|
}
|
|
55
56
|
async function listGitWorktrees(repositoryPath) {
|
|
56
57
|
try {
|
|
57
|
-
|
|
58
|
-
return parseGitWorktreePorcelain(stdout);
|
|
58
|
+
return await readGitWorktrees(repositoryPath);
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
61
|
throw new Error(`Failed to list Git worktrees for ${repositoryPath}: `
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - `restoreAllResourceGc` is the explicit rollback entry point.
|
|
17
17
|
*/
|
|
18
18
|
import { execFileSync } from "node:child_process";
|
|
19
|
+
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
19
20
|
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
20
21
|
import { dirname, join, resolve, relative, isAbsolute } from "node:path";
|
|
21
22
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -384,13 +385,14 @@ function quarantineResource(home, record, now) {
|
|
|
384
385
|
const receiptPath = `${quarantinePath}.receipt.json`;
|
|
385
386
|
try {
|
|
386
387
|
if (isGitWorktreePath(record.path)) {
|
|
388
|
+
const program = supportedGit();
|
|
387
389
|
// Move the linked worktree, including its exact checkout and Git
|
|
388
390
|
// metadata, into the Home-local quarantine. Restore is the inverse
|
|
389
391
|
// move, so the recorded HEAD cannot drift with a branch.
|
|
390
392
|
mkdirSync(quarantineRoot, { recursive: true, mode: 0o700 });
|
|
391
393
|
// Keep the existing SQLite writer fence through this bounded move, so a
|
|
392
394
|
// Task reopen or new durable owner cannot commit between check and move.
|
|
393
|
-
execFileSync(
|
|
395
|
+
execFileSync(program.path, ["-C", record.path, "worktree", "move", "--", record.path, quarantinePath], { timeout: 30_000 });
|
|
394
396
|
writeQuarantineReceipt(home, record, now, "move", receiptPath);
|
|
395
397
|
return {
|
|
396
398
|
ok: true,
|
|
@@ -496,9 +498,10 @@ function restoreQuarantinedRecord(record, liveRefs, now) {
|
|
|
496
498
|
updatedAt: now.toISOString()
|
|
497
499
|
};
|
|
498
500
|
}
|
|
501
|
+
const program = isGitWorktreePath(quarantine.path) ? supportedGit() : undefined;
|
|
499
502
|
mkdirSync(dirname(quarantine.originalPath), { recursive: true });
|
|
500
|
-
if (
|
|
501
|
-
execFileSync(
|
|
503
|
+
if (program !== undefined) {
|
|
504
|
+
execFileSync(program.path, ["-C", quarantine.path, "worktree", "move", "--", quarantine.path, quarantine.originalPath], { timeout: 30_000 });
|
|
502
505
|
}
|
|
503
506
|
else {
|
|
504
507
|
renameSync(quarantine.path, quarantine.originalPath);
|
|
@@ -604,7 +607,7 @@ export async function purgeResourceQuarantine(home, options, store) {
|
|
|
604
607
|
try {
|
|
605
608
|
if (existsSync(quarantine.path)) {
|
|
606
609
|
if (isGitWorktreePath(quarantine.path)) {
|
|
607
|
-
execFileSync(
|
|
610
|
+
execFileSync(supportedGit().path, ["-C", quarantine.path, "worktree", "remove", "--force", "--", quarantine.path], { timeout: 30_000 });
|
|
608
611
|
}
|
|
609
612
|
rmSync(quarantine.path, { recursive: true, force: true });
|
|
610
613
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { execFileSync } from "node:child_process";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { basename, dirname, resolve } from "node:path";
|
|
12
|
+
import { gitOutputLine, supportedGit } from "../repository/gitCompatibility.js";
|
|
12
13
|
import { upsertResourceRecord } from "./resourceRegistry.js";
|
|
13
14
|
import { createResourceRegistryStore } from "./resourceRegistryStore.js";
|
|
14
15
|
import { createResourceRecord, isReleaseNamespacePath, resourceId } from "./resourceTypes.js";
|
|
@@ -212,7 +213,7 @@ function ownerFromManagedWorkspace(home, owner, projectId) {
|
|
|
212
213
|
};
|
|
213
214
|
}
|
|
214
215
|
function readGitWorktreeMetadata(path) {
|
|
215
|
-
const commonDir = execFileSync(
|
|
216
|
+
const commonDir = resolve(path, gitOutputLine(execFileSync(supportedGit().path, ["-C", path, "rev-parse", "--git-common-dir"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })));
|
|
216
217
|
const repositoryPath = basename(commonDir) === ".git" ? dirname(commonDir) : commonDir;
|
|
217
218
|
let branch;
|
|
218
219
|
try {
|
|
@@ -41,6 +41,12 @@ export function createAgentEndpointFactory(start = startStructuredProviderSessio
|
|
|
41
41
|
endpoint.observe(event);
|
|
42
42
|
};
|
|
43
43
|
const opened = await start(payload, {
|
|
44
|
+
// The native TUI owns rendering while attached. Raw JSON mirroring into
|
|
45
|
+
// its inherited PTY would corrupt its screen on every Provider event.
|
|
46
|
+
mirrorOutput: (stream, text) => {
|
|
47
|
+
if (!endpoint?.nativeAccess?.attached)
|
|
48
|
+
(stream === "stdout" ? process.stdout : process.stderr).write(text);
|
|
49
|
+
},
|
|
44
50
|
onAccepted: (value) => emit({ type: "accepted", value }),
|
|
45
51
|
onActivity: (value) => emit({ type: "activity", value }),
|
|
46
52
|
onStarted: (value) => emit({ type: "started", value }),
|
|
@@ -74,6 +80,7 @@ function freezeConfiguration(value) {
|
|
|
74
80
|
class BuiltinAgentEndpoint {
|
|
75
81
|
driver;
|
|
76
82
|
configuration;
|
|
83
|
+
get nativeAccess() { return this.driver.nativeAccess; }
|
|
77
84
|
get ownedProcessId() { return this.driver.ownedProcessId; }
|
|
78
85
|
get nativeAccountHome() { return this.driver.nativeAccountHome; }
|
|
79
86
|
capabilities;
|
|
@@ -118,6 +125,7 @@ class BuiltinAgentEndpoint {
|
|
|
118
125
|
get nativeSessionId() { return this.driver.nativeSessionId; }
|
|
119
126
|
get conversationId() { return this.driver.conversationId; }
|
|
120
127
|
get processInstanceId() { return this.driver.processInstanceId; }
|
|
128
|
+
get publicReply() { return this.driver.publicReply; }
|
|
121
129
|
/**
|
|
122
130
|
* Delegated rather than copied at construction: the Session reads its own
|
|
123
131
|
* current state, and a value captured here would freeze the configuration as
|
|
@@ -18,6 +18,7 @@ import { closeAgentHostEndpoints } from "./agentHostCleanup.js";
|
|
|
18
18
|
import { readAgentRunConfigurationObservation, unknownAgentRunConfiguration } from "./agentRunConfiguration.js";
|
|
19
19
|
import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
|
|
20
20
|
import { runCodexInteractiveHost } from "./codexInteractiveHost.js";
|
|
21
|
+
import { openControlledCodexTui } from "./controlledCodexTui.js";
|
|
21
22
|
import { validateAgentHostLaunchPayload } from "./launchBroker.js";
|
|
22
23
|
import { validateRuntimeProcessExitObservation } from "./processExitObservation.js";
|
|
23
24
|
import { persistRuntimeProcessExitObservation, replayRuntimeProcessExitOutbox } from "./processExitOutbox.js";
|
|
@@ -108,6 +109,10 @@ export async function runAgentHost(input) {
|
|
|
108
109
|
let snapshot = hostSnapshot("idle");
|
|
109
110
|
let dispatchTail = Promise.resolve();
|
|
110
111
|
let promptHuman = () => { };
|
|
112
|
+
let humanConsole;
|
|
113
|
+
let nativeTui;
|
|
114
|
+
let nativeOpening;
|
|
115
|
+
let modelChanging = false;
|
|
111
116
|
const recentSteerAttempts = new Map();
|
|
112
117
|
await replayExitOutbox(input.home);
|
|
113
118
|
const updateSnapshot = (next) => {
|
|
@@ -444,6 +449,8 @@ export async function runAgentHost(input) {
|
|
|
444
449
|
&& providerSession.adapterId === "codex"
|
|
445
450
|
&& !hostStopRequested;
|
|
446
451
|
if (reconnectableCodexClient) {
|
|
452
|
+
nativeTui?.close();
|
|
453
|
+
nativeTui = undefined;
|
|
447
454
|
session = undefined;
|
|
448
455
|
if (codexClientAttachedAt !== undefined
|
|
449
456
|
&& Date.now() - codexClientAttachedAt >= CODEX_CLIENT_STABLE_MS) {
|
|
@@ -1028,7 +1035,128 @@ export async function runAgentHost(input) {
|
|
|
1028
1035
|
promptHuman();
|
|
1029
1036
|
return snapshot;
|
|
1030
1037
|
};
|
|
1031
|
-
const
|
|
1038
|
+
const nativeMutation = async (expected, method, params) => {
|
|
1039
|
+
if (session !== expected || params.threadId !== expected.nativeSessionId || !authority
|
|
1040
|
+
|| (sessionPayload?.environment.YUI_SESSION_SCOPE === "task" && sessionPayload.environment.YUI_ROLE !== "leader")) {
|
|
1041
|
+
throw new Error("Native input targets a replaced or unsupported Session.");
|
|
1042
|
+
}
|
|
1043
|
+
if (method === "turn/interrupt") {
|
|
1044
|
+
if (!activeRunAttemptId || params.turnId !== activeNativeTurnId)
|
|
1045
|
+
throw new Error("Native stop targets a different Turn.");
|
|
1046
|
+
const cancelled = await expected.cancel(activeRunAttemptId);
|
|
1047
|
+
if (cancelled.status !== "requested")
|
|
1048
|
+
throw new Error(`Native stop is ${cancelled.status}; do not replay.`);
|
|
1049
|
+
return {};
|
|
1050
|
+
}
|
|
1051
|
+
if (!Array.isArray(params.input) || params.input.length === 0
|
|
1052
|
+
|| params.input.some(part => !part || typeof part !== "object"
|
|
1053
|
+
|| part.type !== "text" || typeof part.text !== "string")) {
|
|
1054
|
+
throw new Error("Managed native access currently supports text input only.");
|
|
1055
|
+
}
|
|
1056
|
+
const boundedText = params.input.map(part => part.text).join("\n");
|
|
1057
|
+
if (!boundedText.trim() || boundedText.includes("\0") || Buffer.byteLength(boundedText) > 12000)
|
|
1058
|
+
throw new Error("Native input exceeds its text bound.");
|
|
1059
|
+
const run = { attemptId: `human:${authority.holderId}:${randomUUID()}`, boundedText };
|
|
1060
|
+
if (method === "turn/steer") {
|
|
1061
|
+
const turnId = activeNativeTurnId;
|
|
1062
|
+
if (params.expectedTurnId !== turnId || !turnId)
|
|
1063
|
+
throw new Error("Native steer targets a different Turn.");
|
|
1064
|
+
if (!sessionPayload)
|
|
1065
|
+
throw new Error("Native Session launch context is unavailable.");
|
|
1066
|
+
// Persist human intent and the existing steer receipt before Provider
|
|
1067
|
+
// write. A fresh human turn id is not an additional-input binding.
|
|
1068
|
+
const prepared = await callControllerIdempotently(input.home, "runtime.native-steer-prepare", {
|
|
1069
|
+
...hostRunControlParams(sessionPayload, expected.nativeSessionId, authority, run.attemptId),
|
|
1070
|
+
nativeTurnId: turnId, boundedText
|
|
1071
|
+
});
|
|
1072
|
+
const result = await handleControl({ protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "steer-turn",
|
|
1073
|
+
nativeSessionId: expected.nativeSessionId, nativeTurnId: turnId, authority, run: prepared });
|
|
1074
|
+
if (result.outcome !== "accepted")
|
|
1075
|
+
throw new Error("Native steer acceptance is unconfirmed; do not resubmit.");
|
|
1076
|
+
return { turnId };
|
|
1077
|
+
}
|
|
1078
|
+
const result = await handleControl({ protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "submit-turn",
|
|
1079
|
+
nativeSessionId: expected.nativeSessionId, authority, run });
|
|
1080
|
+
const accepted = result.snapshot;
|
|
1081
|
+
if (result.outcome !== "accepted" || accepted.inputAcceptance !== "provider" || !accepted.nativeTurnId) {
|
|
1082
|
+
throw new Error("Native acceptance is unconfirmed; do not resubmit.");
|
|
1083
|
+
}
|
|
1084
|
+
return { turn: { id: accepted.nativeTurnId, items: [], status: "inProgress", error: null } };
|
|
1085
|
+
};
|
|
1086
|
+
const handleControl = async (request) => {
|
|
1087
|
+
if (request.type === "native-model") {
|
|
1088
|
+
return enqueueSerialized(async () => {
|
|
1089
|
+
const current = session;
|
|
1090
|
+
if (!current?.nativeAccess || current.nativeSessionId !== request.nativeSessionId
|
|
1091
|
+
|| !sessionPayload || activeNativeTurnId || authority?.owner !== "controller"
|
|
1092
|
+
|| (sessionPayload.environment.YUI_SESSION_SCOPE === "task" && sessionPayload.environment.YUI_ROLE !== "leader")
|
|
1093
|
+
|| (sessionPayload.environment.YUI_SESSION_SCOPE === "global" && sessionPayload.environment.YUI_ROLE !== "operator")) {
|
|
1094
|
+
throw new Error("Model switching requires the idle current controlled Codex Session, without a native terminal writer.");
|
|
1095
|
+
}
|
|
1096
|
+
if (nativeTui || nativeOpening) {
|
|
1097
|
+
throw new Error("Exit the native TUI before changing the Session model. Closing a browser terminal viewer only detaches it; the Host-owned TUI remains open.");
|
|
1098
|
+
}
|
|
1099
|
+
modelChanging = true;
|
|
1100
|
+
try {
|
|
1101
|
+
await assertHostExecutionEnvironment(input.home, sessionPayload, current.nativeSessionId);
|
|
1102
|
+
try {
|
|
1103
|
+
await current.nativeAccess.setModel(request.model);
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
if (error instanceof CodexPreSubmissionError)
|
|
1107
|
+
throw error;
|
|
1108
|
+
throw new ProviderDeliveryUnknownError(`Native model adoption is unconfirmed: ${errorText(error)}`, `model:${current.nativeSessionId}`, { cause: error });
|
|
1109
|
+
}
|
|
1110
|
+
return controlResult("accepted", { ...snapshot, runConfiguration: current.runConfiguration });
|
|
1111
|
+
}
|
|
1112
|
+
finally {
|
|
1113
|
+
modelChanging = false;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
if (request.type === "native-open" || request.type === "native-respond") {
|
|
1118
|
+
const current = session;
|
|
1119
|
+
if (!current?.nativeAccess || current.nativeSessionId !== request.nativeSessionId
|
|
1120
|
+
|| !sessionPayload || (sessionPayload.environment.YUI_SESSION_SCOPE === "task"
|
|
1121
|
+
&& sessionPayload.environment.YUI_ROLE !== "leader"))
|
|
1122
|
+
throw new Error("Native access requires the current controlled Codex Operator/Leader.");
|
|
1123
|
+
if (request.type === "native-respond") {
|
|
1124
|
+
await assertHostExecutionEnvironment(input.home, sessionPayload, current.nativeSessionId);
|
|
1125
|
+
await current.nativeAccess.respond(request.requestId, request.nativeSessionId, request.nativeTurnId ?? null, request.result);
|
|
1126
|
+
}
|
|
1127
|
+
else if (!nativeTui) {
|
|
1128
|
+
if (modelChanging)
|
|
1129
|
+
throw new Error("Wait for model adoption before attaching native input.");
|
|
1130
|
+
if (!process.stdin.isTTY)
|
|
1131
|
+
throw new Error("Native TUI requires the Host's owned terminal.");
|
|
1132
|
+
humanConsole?.close();
|
|
1133
|
+
humanConsole = undefined;
|
|
1134
|
+
nativeOpening ??= openControlledCodexTui(sessionPayload, current.nativeSessionId, current.nativeAccess, {
|
|
1135
|
+
mutate: (method, params) => nativeMutation(current, method, params),
|
|
1136
|
+
respond: async (requestId, nativeTurnId, result) => {
|
|
1137
|
+
await handleControl({ protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "native-respond",
|
|
1138
|
+
nativeSessionId: current.nativeSessionId, requestId, nativeTurnId, result });
|
|
1139
|
+
},
|
|
1140
|
+
onExit: () => {
|
|
1141
|
+
nativeTui = undefined;
|
|
1142
|
+
process.stdout.write("\r\nNative TUI detached. Reattach the terminal to reopen this same Thread; the Agent Host remains running.\r\n");
|
|
1143
|
+
},
|
|
1144
|
+
onError: error => process.stderr.write(`Native TUI: ${errorText(error)}\n`)
|
|
1145
|
+
});
|
|
1146
|
+
try {
|
|
1147
|
+
nativeTui = await nativeOpening;
|
|
1148
|
+
}
|
|
1149
|
+
finally {
|
|
1150
|
+
nativeOpening = undefined;
|
|
1151
|
+
}
|
|
1152
|
+
if (session !== current) {
|
|
1153
|
+
nativeTui.close();
|
|
1154
|
+
nativeTui = undefined;
|
|
1155
|
+
throw new Error("Session changed while opening native access.");
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return controlResult("accepted", { ...snapshot, nativeTui: !!nativeTui });
|
|
1159
|
+
}
|
|
1032
1160
|
if (request.type === "status") {
|
|
1033
1161
|
// Read the Agent's configuration at answer time, not from the stored
|
|
1034
1162
|
// snapshot. The Agent may have changed it since the last state
|
|
@@ -1036,10 +1164,20 @@ export async function runAgentHost(input) {
|
|
|
1036
1164
|
// current reading rather than the one that was true at launch. Nothing is
|
|
1037
1165
|
// sent to the Agent to obtain it: this reads what the Session has already
|
|
1038
1166
|
// been told.
|
|
1167
|
+
let requestBytes = 0;
|
|
1168
|
+
const nativeRequests = session?.nativeAccess?.requests.filter(request => {
|
|
1169
|
+
requestBytes += Buffer.byteLength(JSON.stringify(request));
|
|
1170
|
+
return requestBytes <= 8000;
|
|
1171
|
+
}).slice(0, 8);
|
|
1039
1172
|
return controlResult("status", session === undefined
|
|
1040
1173
|
? snapshot
|
|
1041
1174
|
: validateSnapshot({
|
|
1042
1175
|
...snapshot,
|
|
1176
|
+
nativeTui: !!nativeTui,
|
|
1177
|
+
...(nativeRequests === undefined ? {} : { nativeRequests,
|
|
1178
|
+
nativeRequestsOmitted: nativeRequests.length !== session.nativeAccess.requests.length }),
|
|
1179
|
+
...(session.publicReply === undefined ? {} : { publicReply: session.publicReply }),
|
|
1180
|
+
...(session.adapterId === "codex" ? { publicReplyObservation: "supported" } : {}),
|
|
1043
1181
|
runConfiguration: session.runConfiguration
|
|
1044
1182
|
}));
|
|
1045
1183
|
}
|
|
@@ -1138,6 +1276,8 @@ export async function runAgentHost(input) {
|
|
|
1138
1276
|
const accepted = await enqueueSerialized(async () => setAuthority(request));
|
|
1139
1277
|
return controlResult("accepted", accepted);
|
|
1140
1278
|
}
|
|
1279
|
+
if (request.type !== "launch")
|
|
1280
|
+
throw new Error("Unsupported Host request.");
|
|
1141
1281
|
const redeemed = await redeem(input.home, request.ticket);
|
|
1142
1282
|
if (activeRunPayload !== undefined
|
|
1143
1283
|
|| ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
|
|
@@ -1149,8 +1289,9 @@ export async function runAgentHost(input) {
|
|
|
1149
1289
|
}
|
|
1150
1290
|
const accepted = await enqueueDispatch(redeemed);
|
|
1151
1291
|
return controlResult("accepted", accepted);
|
|
1152
|
-
}
|
|
1153
|
-
const
|
|
1292
|
+
};
|
|
1293
|
+
const control = await openAgentHostControl(input.home, payload, () => snapshot, handleControl);
|
|
1294
|
+
humanConsole = process.stdin.isTTY
|
|
1154
1295
|
? createInterface({ input: process.stdin, output: process.stdout, terminal: true })
|
|
1155
1296
|
: undefined;
|
|
1156
1297
|
promptHuman = () => {
|
|
@@ -1231,6 +1372,7 @@ export async function runAgentHost(input) {
|
|
|
1231
1372
|
if (forceKillTimer !== undefined)
|
|
1232
1373
|
clearTimeout(forceKillTimer);
|
|
1233
1374
|
humanConsole?.close();
|
|
1375
|
+
nativeTui?.close();
|
|
1234
1376
|
await closeAgentHostEndpoints({
|
|
1235
1377
|
owner: endpointOwner, session, lease: endpointLease, adapterId: endpointAdapterId,
|
|
1236
1378
|
attemptId: activeRunAttemptId ?? lastTerminal?.attemptId,
|
|
@@ -1309,6 +1451,9 @@ export async function sendAgentHostAuthorityControl(input) {
|
|
|
1309
1451
|
export async function sendAgentHostCancelControl(input) {
|
|
1310
1452
|
return await sendAgentHostControl(input);
|
|
1311
1453
|
}
|
|
1454
|
+
export async function sendAgentHostNativeControl(input) {
|
|
1455
|
+
return sendAgentHostControl(input);
|
|
1456
|
+
}
|
|
1312
1457
|
export async function inspectAgentHost(input) {
|
|
1313
1458
|
const result = await sendAgentHostControl({
|
|
1314
1459
|
...input,
|
|
@@ -1550,6 +1695,12 @@ function boundControlResponse(result) {
|
|
|
1550
1695
|
};
|
|
1551
1696
|
if (withinControlBound(result))
|
|
1552
1697
|
return result;
|
|
1698
|
+
if (result.snapshot.publicReply !== undefined) {
|
|
1699
|
+
const { publicReply: _omitted, ...snapshot } = result.snapshot;
|
|
1700
|
+
result = { ...result, snapshot };
|
|
1701
|
+
if (withinControlBound(result))
|
|
1702
|
+
return result;
|
|
1703
|
+
}
|
|
1553
1704
|
const clip = (text, chars) => {
|
|
1554
1705
|
if (text.length <= chars)
|
|
1555
1706
|
return text;
|
|
@@ -1613,6 +1764,22 @@ function validateControl(control) {
|
|
|
1613
1764
|
}
|
|
1614
1765
|
if (control.type === "status")
|
|
1615
1766
|
return Object.freeze({ ...control });
|
|
1767
|
+
if (control.type === "native-open" || control.type === "native-respond" || control.type === "native-model") {
|
|
1768
|
+
validateIdentity(control.nativeSessionId, "native Session id");
|
|
1769
|
+
if (control.type === "native-model" && (typeof control.model !== "string"
|
|
1770
|
+
|| !control.model.trim() || control.model.length > 200 || control.model.includes("\0"))) {
|
|
1771
|
+
throw new Error("Invalid native model.");
|
|
1772
|
+
}
|
|
1773
|
+
if (control.type === "native-respond") {
|
|
1774
|
+
if (control.nativeTurnId !== null)
|
|
1775
|
+
validateIdentity(control.nativeTurnId, "native Turn id");
|
|
1776
|
+
if ((typeof control.requestId !== "string" && typeof control.requestId !== "number")
|
|
1777
|
+
|| control.result === null || typeof control.result !== "object" || Array.isArray(control.result)) {
|
|
1778
|
+
throw new Error("Invalid native request response.");
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return Object.freeze({ ...control });
|
|
1782
|
+
}
|
|
1616
1783
|
if (control.type === "cancel") {
|
|
1617
1784
|
if (control.nativeOnly !== undefined && typeof control.nativeOnly !== "boolean") {
|
|
1618
1785
|
throw new Error("Native-only cancellation flag is invalid.");
|
|
@@ -42,7 +42,7 @@ export class CodexAppServerRuntime {
|
|
|
42
42
|
cwd: text(input.cwd, "Codex thread cwd"),
|
|
43
43
|
...threadOptions(input)
|
|
44
44
|
});
|
|
45
|
-
return { conversationId: threadId(result) };
|
|
45
|
+
return { conversationId: threadId(result), raw: result };
|
|
46
46
|
}
|
|
47
47
|
async resumeConversation(conversationId, options = {}) {
|
|
48
48
|
const id = text(conversationId, "Codex thread id");
|