codex-chatgpt-control 0.1.0-alpha.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/contracts/v1/fixtures/backend-capabilities.json +54 -0
- package/contracts/v1/fixtures/backend-error-event-missing-stream-input.json +10 -0
- package/contracts/v1/fixtures/backend-error-missing-run-input.json +10 -0
- package/contracts/v1/fixtures/backend-runner-plan-request.json +12 -0
- package/contracts/v1/fixtures/backend-version.json +10 -0
- package/contracts/v1/fixtures/command-descriptors.json +803 -0
- package/contracts/v1/fixtures/describe-runner-run.json +31 -0
- package/contracts/v1/fixtures/doctor-bridge-upload.json +34 -0
- package/contracts/v1/fixtures/help-root.json +6 -0
- package/contracts/v1/fixtures/named-plan-two-turn.json +39 -0
- package/contracts/v1/fixtures/output-json-parse-success.json +115 -0
- package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +43 -0
- package/contracts/v1/fixtures/report-redaction-default.json +16 -0
- package/contracts/v1/fixtures/reports-create-redacted.json +16 -0
- package/contracts/v1/fixtures/reports-summarize-redacted.json +26 -0
- package/contracts/v1/fixtures/responses-basic-success.json +27 -0
- package/contracts/v1/fixtures/responses-hidden-instructions-unsupported.json +22 -0
- package/contracts/v1/fixtures/responses-unknown-field-unsupported.json +22 -0
- package/contracts/v1/fixtures/responses-unsupported-previous-response-id.json +22 -0
- package/contracts/v1/fixtures/responses-unsupported-temperature.json +22 -0
- package/contracts/v1/fixtures/run-basic-success.json +36 -0
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +180 -0
- package/contracts/v1/fixtures/run-file-attach-success.json +63 -0
- package/contracts/v1/fixtures/run-report-redacted.json +52 -0
- package/contracts/v1/fixtures/run-selector-drift-blocker.json +60 -0
- package/contracts/v1/fixtures/run-timeout-partial.json +81 -0
- package/contracts/v1/fixtures/run-two-turn-success.json +83 -0
- package/contracts/v1/fixtures/run-upload-permission-blocker.json +56 -0
- package/contracts/v1/fixtures/runner-budget-blocker.json +106 -0
- package/contracts/v1/fixtures/runner-full-agent-config.json +50 -0
- package/contracts/v1/fixtures/runner-input-items-and-files-plan.json +53 -0
- package/contracts/v1/fixtures/runner-metadata-only-plan.json +31 -0
- package/contracts/v1/fixtures/runner-visible-prefix-plan.json +28 -0
- package/contracts/v1/fixtures/runner-visible-setup-plan.json +37 -0
- package/contracts/v1/fixtures/stream-basic.ndjson +3 -0
- package/contracts/v1/fixtures/stream-blocked.ndjson +2 -0
- package/contracts/v1/fixtures/stream-submitted-completed.ndjson +3 -0
- package/contracts/v1/fixtures/workflow-ask-success.json +67 -0
- package/contracts/v1/manifest.json +210 -0
- package/contracts/v1/parity-suite.json +635 -0
- package/contracts/v1/schemas/agent.schema.json +52 -0
- package/contracts/v1/schemas/backend-event.schema.json +74 -0
- package/contracts/v1/schemas/backend-request.schema.json +59 -0
- package/contracts/v1/schemas/backend-response.schema.json +42 -0
- package/contracts/v1/schemas/capabilities.schema.json +33 -0
- package/contracts/v1/schemas/command-descriptor.schema.json +31 -0
- package/contracts/v1/schemas/command-result.schema.json +99 -0
- package/contracts/v1/schemas/manifest.schema.json +69 -0
- package/contracts/v1/schemas/response.schema.json +51 -0
- package/contracts/v1/schemas/run-result.schema.json +138 -0
- package/contracts/v1/schemas/sequence-plan.schema.json +44 -0
- package/contracts/v1/schemas/stream-event.schema.json +25 -0
- package/dist/codex-chatgpt-control-backend.mjs +5284 -0
- package/dist/codex-chatgpt-control.bundle.mjs +6013 -0
- package/dist/src/backend/client.d.ts +129 -0
- package/dist/src/backend/client.js +415 -0
- package/dist/src/backend/protocol.d.ts +74 -0
- package/dist/src/backend/protocol.js +128 -0
- package/dist/src/backend/session.d.ts +11 -0
- package/dist/src/backend/session.js +223 -0
- package/dist/src/backend/stdio-server.d.ts +9 -0
- package/dist/src/backend/stdio-server.js +88 -0
- package/dist/src/browser/attach.d.ts +8 -0
- package/dist/src/browser/attach.js +456 -0
- package/dist/src/browser/clipboard.d.ts +2 -0
- package/dist/src/browser/clipboard.js +26 -0
- package/dist/src/browser/downloads.d.ts +7 -0
- package/dist/src/browser/downloads.js +29 -0
- package/dist/src/browser/page-state.d.ts +17 -0
- package/dist/src/browser/page-state.js +66 -0
- package/dist/src/client.d.ts +167 -0
- package/dist/src/client.js +666 -0
- package/dist/src/commands/confirmations.d.ts +9 -0
- package/dist/src/commands/confirmations.js +33 -0
- package/dist/src/commands/context.d.ts +2 -0
- package/dist/src/commands/context.js +32 -0
- package/dist/src/commands/doctor.d.ts +16 -0
- package/dist/src/commands/doctor.js +120 -0
- package/dist/src/commands/files.d.ts +4 -0
- package/dist/src/commands/files.js +314 -0
- package/dist/src/commands/helpers.d.ts +16 -0
- package/dist/src/commands/helpers.js +128 -0
- package/dist/src/commands/messages.d.ts +16 -0
- package/dist/src/commands/messages.js +538 -0
- package/dist/src/commands/modes.d.ts +9 -0
- package/dist/src/commands/modes.js +323 -0
- package/dist/src/commands/output.d.ts +5 -0
- package/dist/src/commands/output.js +31 -0
- package/dist/src/commands/registry.d.ts +17 -0
- package/dist/src/commands/registry.js +275 -0
- package/dist/src/commands/reports.d.ts +14 -0
- package/dist/src/commands/reports.js +47 -0
- package/dist/src/commands/response-actions.d.ts +2 -0
- package/dist/src/commands/response-actions.js +141 -0
- package/dist/src/commands/sequence.d.ts +9 -0
- package/dist/src/commands/sequence.js +228 -0
- package/dist/src/commands/session.d.ts +2 -0
- package/dist/src/commands/session.js +25 -0
- package/dist/src/commands/threads.d.ts +6 -0
- package/dist/src/commands/threads.js +300 -0
- package/dist/src/dom/fixtures.d.ts +1 -0
- package/dist/src/dom/fixtures.js +8 -0
- package/dist/src/dom/menus.d.ts +8 -0
- package/dist/src/dom/menus.js +40 -0
- package/dist/src/dom/message-format.d.ts +30 -0
- package/dist/src/dom/message-format.js +593 -0
- package/dist/src/dom/messages.d.ts +40 -0
- package/dist/src/dom/messages.js +127 -0
- package/dist/src/dom/selectors.d.ts +20 -0
- package/dist/src/dom/selectors.js +72 -0
- package/dist/src/dom/visible-text.d.ts +5 -0
- package/dist/src/dom/visible-text.js +27 -0
- package/dist/src/errors.d.ts +32 -0
- package/dist/src/errors.js +127 -0
- package/dist/src/index.d.ts +34 -0
- package/dist/src/index.js +34 -0
- package/dist/src/logger.d.ts +15 -0
- package/dist/src/logger.js +25 -0
- package/dist/src/runner/agent.d.ts +2 -0
- package/dist/src/runner/agent.js +17 -0
- package/dist/src/runner/index.d.ts +7 -0
- package/dist/src/runner/index.js +7 -0
- package/dist/src/runner/interruptions.d.ts +3 -0
- package/dist/src/runner/interruptions.js +65 -0
- package/dist/src/runner/responses.d.ts +30 -0
- package/dist/src/runner/responses.js +171 -0
- package/dist/src/runner/result.d.ts +3 -0
- package/dist/src/runner/result.js +128 -0
- package/dist/src/runner/resume.d.ts +11 -0
- package/dist/src/runner/resume.js +26 -0
- package/dist/src/runner/stream.d.ts +13 -0
- package/dist/src/runner/stream.js +66 -0
- package/dist/src/runner/types.d.ts +264 -0
- package/dist/src/runner/types.js +1 -0
- package/dist/src/safety/blockers.d.ts +7 -0
- package/dist/src/safety/blockers.js +60 -0
- package/dist/src/safety/redaction.d.ts +2 -0
- package/dist/src/safety/redaction.js +18 -0
- package/dist/src/safety/report-redaction.d.ts +8 -0
- package/dist/src/safety/report-redaction.js +74 -0
- package/dist/src/safety/risk.d.ts +26 -0
- package/dist/src/safety/risk.js +28 -0
- package/dist/src/scripts/backend-server.d.ts +2 -0
- package/dist/src/scripts/backend-server.js +7 -0
- package/dist/src/scripts/check-parity-suite.d.ts +1 -0
- package/dist/src/scripts/check-parity-suite.js +10 -0
- package/dist/src/scripts/continue-thread.d.ts +27 -0
- package/dist/src/scripts/continue-thread.js +388 -0
- package/dist/src/scripts/live-smoke/harness.d.ts +11 -0
- package/dist/src/scripts/live-smoke/harness.js +151 -0
- package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
- package/dist/src/scripts/live-smoke/scenarios.js +648 -0
- package/dist/src/scripts/live-smoke/types.d.ts +53 -0
- package/dist/src/scripts/live-smoke/types.js +1 -0
- package/dist/src/scripts/live-smoke-module.d.ts +3 -0
- package/dist/src/scripts/live-smoke-module.js +2 -0
- package/dist/src/scripts/live-smoke.d.ts +1 -0
- package/dist/src/scripts/live-smoke.js +54 -0
- package/dist/src/scripts/smoke-attach-file.d.ts +1 -0
- package/dist/src/scripts/smoke-attach-file.js +39 -0
- package/dist/src/scripts/smoke-hi.d.ts +1 -0
- package/dist/src/scripts/smoke-hi.js +25 -0
- package/dist/src/scripts/smoke-search-open-read.d.ts +1 -0
- package/dist/src/scripts/smoke-search-open-read.js +41 -0
- package/dist/src/testing/parity-suite.d.ts +12 -0
- package/dist/src/testing/parity-suite.js +213 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.js +1 -0
- package/package.json +81 -0
- package/references/agents-runner.md +32 -0
- package/references/backend-protocol.md +238 -0
- package/references/python-parity.md +168 -0
- package/references/responses-adapter.md +28 -0
- package/references/safety.md +14 -0
- package/references/streaming.md +15 -0
- package/references/troubleshooting.md +91 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
import { attachFiles, downloadLatestFile } from "./commands/files.js";
|
|
2
|
+
import { doctor } from "./commands/doctor.js";
|
|
3
|
+
import { askMessage, composeMessage, readLatest, submitMessage, waitAndRead, waitForMessage } from "./commands/messages.js";
|
|
4
|
+
import { selectTool, setMode } from "./commands/modes.js";
|
|
5
|
+
import { createRunReport } from "./commands/reports.js";
|
|
6
|
+
import { copyResponse } from "./commands/response-actions.js";
|
|
7
|
+
import { commandDescriptors, describeCommand, helpText } from "./commands/registry.js";
|
|
8
|
+
import { runSequence } from "./commands/sequence.js";
|
|
9
|
+
import { bootstrap } from "./commands/session.js";
|
|
10
|
+
import { newThread, openThread, searchThreads } from "./commands/threads.js";
|
|
11
|
+
import { resultError, resultOk } from "./errors.js";
|
|
12
|
+
import { createChatGPTAgent } from "./runner/agent.js";
|
|
13
|
+
import { toRunResult } from "./runner/result.js";
|
|
14
|
+
import { responseFromRunResult, responsesCreateArgsToRunInput, unsupportedResponse, validateResponsesCreateArgs } from "./runner/responses.js";
|
|
15
|
+
import { streamFromRunResult } from "./runner/stream.js";
|
|
16
|
+
import { redactReportValue } from "./safety/report-redaction.js";
|
|
17
|
+
export function createChatGPT(options = {}) {
|
|
18
|
+
const env = runtimeEnv(options);
|
|
19
|
+
const limits = normalizeLimits(options.limits);
|
|
20
|
+
const runnerRun = ((agent, input, runnerOptions) => {
|
|
21
|
+
const run = () => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting);
|
|
22
|
+
return runnerOptions?.stream === true ? streamFromRunResult(run) : run();
|
|
23
|
+
});
|
|
24
|
+
const runner = {
|
|
25
|
+
run: runnerRun,
|
|
26
|
+
plan: (agent, input) => planAgentWorkflow(agent, input, options.defaults)
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
agent: config => createChatGPTAgent(config),
|
|
30
|
+
run: runner.run,
|
|
31
|
+
runner,
|
|
32
|
+
responses: {
|
|
33
|
+
create: args => createResponse(args, runner, env.now)
|
|
34
|
+
},
|
|
35
|
+
ask: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
|
|
36
|
+
askInThread: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
|
|
37
|
+
askWithFiles: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
|
|
38
|
+
askAndDownload: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
|
|
39
|
+
runMessages: args => runGuarded(planRunMessages(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
|
|
40
|
+
openThread: thread => runSequence(planOpenThread(thread), env),
|
|
41
|
+
readLatest: args => readLatest(env, args),
|
|
42
|
+
copyLatest: args => copyResponse(env, args),
|
|
43
|
+
downloadLatest: args => downloadLatestFile(env, args),
|
|
44
|
+
runPlan: plan => runPlanInvocation(plan, env, limits, options.defaults, options.reporting),
|
|
45
|
+
doctor: args => doctor(env, args),
|
|
46
|
+
createReport: (result, args) => createRunReport(env, result, args ?? options.reporting ?? {}),
|
|
47
|
+
reports: {
|
|
48
|
+
create: (result, args) => createRunReport(env, result, args ?? options.reporting ?? {}),
|
|
49
|
+
redact: async (value, args) => resultOk(redactReportValue(value, args), {}),
|
|
50
|
+
summarize: async (result, args) => resultOk(redactReportValue(resultSummary(result), args), {})
|
|
51
|
+
},
|
|
52
|
+
plan: (name, args) => planByName(name, args, options.defaults),
|
|
53
|
+
commands: filter => commandDescriptors().filter(descriptor => filter?.layer === undefined || descriptor.layer === filter.layer),
|
|
54
|
+
describe: name => describeCommand(name),
|
|
55
|
+
help: topic => helpText(topic),
|
|
56
|
+
session: {
|
|
57
|
+
bootstrap: args => bootstrap(env, args)
|
|
58
|
+
},
|
|
59
|
+
threads: {
|
|
60
|
+
new: args => newThread(env, args),
|
|
61
|
+
search: args => searchThreads(env, args),
|
|
62
|
+
open: args => openThread(env, args)
|
|
63
|
+
},
|
|
64
|
+
messages: {
|
|
65
|
+
compose: args => composeMessage(env, args),
|
|
66
|
+
submit: args => submitMessage(env, args),
|
|
67
|
+
ask: args => askMessage(env, args),
|
|
68
|
+
wait: args => waitForMessage(env, args),
|
|
69
|
+
readLatest: args => readLatest(env, args),
|
|
70
|
+
waitAndRead: args => waitAndRead(env, args)
|
|
71
|
+
},
|
|
72
|
+
files: {
|
|
73
|
+
attach: args => attachFiles(env, args),
|
|
74
|
+
downloadLatest: args => downloadLatestFile(env, args)
|
|
75
|
+
},
|
|
76
|
+
modes: {
|
|
77
|
+
set: args => setMode(env, args)
|
|
78
|
+
},
|
|
79
|
+
tools: {
|
|
80
|
+
select: args => selectTool(env, args)
|
|
81
|
+
},
|
|
82
|
+
response: {
|
|
83
|
+
copy: args => copyResponse(env, args)
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function runGuarded(plan, env, limits, report) {
|
|
88
|
+
const budget = checkRunBudget(plan, limits);
|
|
89
|
+
if (budget !== undefined)
|
|
90
|
+
return budget;
|
|
91
|
+
const result = await runSequence(plan, env);
|
|
92
|
+
if (report === undefined || report.enabled === false)
|
|
93
|
+
return result;
|
|
94
|
+
const reportResult = await createRunReport(env, result, capReportOptions(report, limits));
|
|
95
|
+
if (reportResult.ok && reportResult.data !== undefined) {
|
|
96
|
+
if (reportResult.data.bytes > limits.maxReportBytesPerRun) {
|
|
97
|
+
const overBudget = {
|
|
98
|
+
ok: false,
|
|
99
|
+
status: "needs_confirmation",
|
|
100
|
+
warnings: [`Run report exceeded byte budget after creation: ${reportResult.data.bytes}/${limits.maxReportBytesPerRun}.`],
|
|
101
|
+
reportPath: reportResult.data.path,
|
|
102
|
+
blocker: {
|
|
103
|
+
kind: "confirmation",
|
|
104
|
+
code: "report_byte_budget_exceeded",
|
|
105
|
+
fieldPath: "limits.maxReportBytesPerRun",
|
|
106
|
+
message: `Workflow "${plan.name}" created a report larger than the configured budget (${reportResult.data.bytes}/${limits.maxReportBytesPerRun} bytes). Ask the user before preserving or sharing it.`,
|
|
107
|
+
remediation: [
|
|
108
|
+
{
|
|
109
|
+
label: "Confirm report retention",
|
|
110
|
+
instruction: "Ask the user whether to keep this report, increase maxReportBytesPerRun, or rerun with a smaller report preview.",
|
|
111
|
+
userActionRequired: true
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
resumable: true
|
|
115
|
+
},
|
|
116
|
+
context: result.context
|
|
117
|
+
};
|
|
118
|
+
if (result.steps !== undefined)
|
|
119
|
+
overBudget.steps = result.steps;
|
|
120
|
+
return overBudget;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
...result,
|
|
124
|
+
reportPath: reportResult.data.path,
|
|
125
|
+
warnings: [...result.warnings, ...reportResult.warnings]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
...result,
|
|
130
|
+
warnings: [
|
|
131
|
+
...result.warnings,
|
|
132
|
+
`Run report creation failed: ${reportResult.error?.message ?? reportResult.blocker?.message ?? reportResult.status}`
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function normalizeLimits(limits) {
|
|
137
|
+
return {
|
|
138
|
+
maxPromptsPerRun: limits?.maxPromptsPerRun ?? 5,
|
|
139
|
+
maxThreadsOpenedPerRun: limits?.maxThreadsOpenedPerRun ?? 3,
|
|
140
|
+
maxMessagesReadPerRun: limits?.maxMessagesReadPerRun ?? 10,
|
|
141
|
+
maxReportBytesPerRun: limits?.maxReportBytesPerRun ?? 2_000_000,
|
|
142
|
+
maxReportPreviewChars: limits?.maxReportPreviewChars ?? 240
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function checkRunBudget(plan, limits) {
|
|
146
|
+
const prompts = plan.steps.filter(step => step.command === "messages.ask" || step.command === "messages.submit").length;
|
|
147
|
+
const threads = plan.steps.filter(step => step.command === "threads.new" || step.command === "threads.open").length;
|
|
148
|
+
const reads = plan.steps.filter(step => step.command === "messages.readLatest" || step.command === "messages.waitAndRead" || step.command === "response.copy").length
|
|
149
|
+
+ plan.steps.filter(step => step.command === "messages.ask" && askStepReads(step.args)).length;
|
|
150
|
+
const violations = [];
|
|
151
|
+
if (prompts > limits.maxPromptsPerRun)
|
|
152
|
+
violations.push(`prompts ${prompts}/${limits.maxPromptsPerRun}`);
|
|
153
|
+
if (threads > limits.maxThreadsOpenedPerRun)
|
|
154
|
+
violations.push(`threads ${threads}/${limits.maxThreadsOpenedPerRun}`);
|
|
155
|
+
if (reads > limits.maxMessagesReadPerRun)
|
|
156
|
+
violations.push(`reads ${reads}/${limits.maxMessagesReadPerRun}`);
|
|
157
|
+
if (violations.length === 0)
|
|
158
|
+
return undefined;
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
status: "needs_confirmation",
|
|
162
|
+
warnings: [],
|
|
163
|
+
blocker: {
|
|
164
|
+
kind: "confirmation",
|
|
165
|
+
code: "run_budget_exceeded",
|
|
166
|
+
fieldPath: "limits",
|
|
167
|
+
message: `Workflow "${plan.name}" exceeds ChatGPT browser-control run budget: ${violations.join(", ")}. Ask the user to confirm a bounded exception.`,
|
|
168
|
+
remediation: [
|
|
169
|
+
{
|
|
170
|
+
label: "Confirm bounded run",
|
|
171
|
+
instruction: "Ask the user to approve this specific over-budget run, or reduce the number of prompts, thread opens, or message reads.",
|
|
172
|
+
userActionRequired: true
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
resumable: true
|
|
176
|
+
},
|
|
177
|
+
context: { timestamp: new Date().toISOString() }
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function askStepReads(args) {
|
|
181
|
+
return args.read === true || typeof args.read === "object";
|
|
182
|
+
}
|
|
183
|
+
function reportOptions(request, defaults) {
|
|
184
|
+
if (request === false)
|
|
185
|
+
return undefined;
|
|
186
|
+
if (request === true)
|
|
187
|
+
return { ...(defaults ?? {}), enabled: true };
|
|
188
|
+
if (request !== undefined)
|
|
189
|
+
return { ...(defaults ?? {}), ...request, enabled: request.enabled ?? true };
|
|
190
|
+
return defaults?.enabled === true ? defaults : undefined;
|
|
191
|
+
}
|
|
192
|
+
function capReportOptions(report, limits) {
|
|
193
|
+
return {
|
|
194
|
+
...report,
|
|
195
|
+
maxPreviewChars: Math.min(report.maxPreviewChars ?? limits.maxReportPreviewChars, limits.maxReportPreviewChars)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async function createResponse(args, runner, now) {
|
|
199
|
+
const validation = validateResponsesCreateArgs(args);
|
|
200
|
+
const timestamp = now?.() ?? new Date();
|
|
201
|
+
if (!validation.ok) {
|
|
202
|
+
return unsupportedResponse(validation.unsupported, timestamp);
|
|
203
|
+
}
|
|
204
|
+
const responseArgs = args;
|
|
205
|
+
const agentConfig = {
|
|
206
|
+
name: "responses-adapter",
|
|
207
|
+
instructionsMode: responseArgs.instructionsMode === "visible_prefix" ? "visible_prefix" : "metadata_only"
|
|
208
|
+
};
|
|
209
|
+
if (typeof responseArgs.instructions === "string") {
|
|
210
|
+
agentConfig.instructions = responseArgs.instructions;
|
|
211
|
+
}
|
|
212
|
+
const agent = createChatGPTAgent(agentConfig);
|
|
213
|
+
const result = await runner.run(agent, responsesCreateArgsToRunInput(responseArgs));
|
|
214
|
+
return responseFromRunResult(result, now?.() ?? timestamp);
|
|
215
|
+
}
|
|
216
|
+
async function runAgentWorkflow(agent, input, env, limits, defaults, reporting) {
|
|
217
|
+
try {
|
|
218
|
+
const normalized = normalizeRunnerInput(agent, input);
|
|
219
|
+
const plan = planAgentWorkflowFromNormalized(agent, normalized, defaults);
|
|
220
|
+
const report = reportOptions(normalized.report ?? agent.defaults.report, reporting);
|
|
221
|
+
const result = await runGuarded(plan, env, limits, report);
|
|
222
|
+
return toRunResult(agent, result);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return toRunResult(agent, resultError(error instanceof Error ? error : new Error(String(error)), {}));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function planAgentWorkflow(agent, input, defaults = {}) {
|
|
229
|
+
return planAgentWorkflowFromNormalized(agent, normalizeRunnerInput(agent, input), defaults);
|
|
230
|
+
}
|
|
231
|
+
function planAgentWorkflowFromNormalized(agent, input, defaults = {}) {
|
|
232
|
+
const wait = input.wait ?? agent.defaults.wait ?? defaults.wait ?? true;
|
|
233
|
+
const read = input.read ?? agent.defaults.read ?? defaults.read ?? { format: "markdown" };
|
|
234
|
+
const thread = input.thread ?? agent.defaults.thread ?? { type: "new" };
|
|
235
|
+
const steps = [
|
|
236
|
+
bootstrapStepForWorkflow(thread, input.existingTab ?? agent.defaults.existingTab ?? defaults.existingTab, input.preferExistingTab ?? agent.defaults.preferExistingTab ?? defaults.preferExistingTab),
|
|
237
|
+
...threadSteps(thread)
|
|
238
|
+
];
|
|
239
|
+
const mode = input.mode ?? agent.defaults.mode ?? defaults.mode;
|
|
240
|
+
if (mode !== undefined) {
|
|
241
|
+
steps.push({ id: "mode", command: "modes.set", args: mode });
|
|
242
|
+
}
|
|
243
|
+
for (const [index, tool] of input.tools.entries()) {
|
|
244
|
+
steps.push({ id: `tool${index + 1}`, command: "tools.select", args: tool });
|
|
245
|
+
}
|
|
246
|
+
if (input.files.length > 0) {
|
|
247
|
+
steps.push({ id: "attach", command: "files.attach", args: { paths: input.files } });
|
|
248
|
+
}
|
|
249
|
+
if (agent.instructionsMode === "visible_setup_message" && hasInstructions(agent)) {
|
|
250
|
+
steps.push({
|
|
251
|
+
id: "agent_setup",
|
|
252
|
+
command: "messages.ask",
|
|
253
|
+
args: {
|
|
254
|
+
text: renderAgentSetupMessage(agent),
|
|
255
|
+
wait,
|
|
256
|
+
read: false
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
steps.push({
|
|
261
|
+
id: "ask",
|
|
262
|
+
command: "messages.ask",
|
|
263
|
+
args: {
|
|
264
|
+
text: renderRunnerPrompt(agent, input.prompt),
|
|
265
|
+
wait,
|
|
266
|
+
read
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if (input.copy !== undefined && input.copy !== false) {
|
|
270
|
+
steps.push({ id: "copy", command: "response.copy", args: input.copy });
|
|
271
|
+
}
|
|
272
|
+
if (input.download !== undefined && input.download !== false) {
|
|
273
|
+
steps.push({ id: "download", command: "files.downloadLatest", args: input.download });
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
name: `agent-run:${agent.name}`,
|
|
277
|
+
policy: { stopOnError: true, returnPartial: true },
|
|
278
|
+
steps
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function normalizeRunnerInput(agent, input) {
|
|
282
|
+
const args = typeof input === "string" ? { input } : input;
|
|
283
|
+
const collected = collectRunnerInput(args.input);
|
|
284
|
+
const attachments = normalizeRunnerAttachments(args.attachments);
|
|
285
|
+
const mode = args.mode;
|
|
286
|
+
const normalized = {
|
|
287
|
+
prompt: collected.prompt,
|
|
288
|
+
tools: args.tools ?? [],
|
|
289
|
+
files: [...collected.files, ...attachments]
|
|
290
|
+
};
|
|
291
|
+
if (args.thread !== undefined)
|
|
292
|
+
normalized.thread = args.thread;
|
|
293
|
+
if (args.existingTab !== undefined)
|
|
294
|
+
normalized.existingTab = args.existingTab;
|
|
295
|
+
if (args.preferExistingTab !== undefined)
|
|
296
|
+
normalized.preferExistingTab = args.preferExistingTab;
|
|
297
|
+
if (mode !== undefined)
|
|
298
|
+
normalized.mode = mode;
|
|
299
|
+
if (args.response !== undefined)
|
|
300
|
+
normalized.read = args.response;
|
|
301
|
+
if (args.download !== undefined)
|
|
302
|
+
normalized.download = args.download;
|
|
303
|
+
if (args.copy !== undefined)
|
|
304
|
+
normalized.copy = args.copy;
|
|
305
|
+
if (args.report !== undefined)
|
|
306
|
+
normalized.report = args.report;
|
|
307
|
+
if (normalized.prompt.trim().length === 0) {
|
|
308
|
+
throw new Error(`ChatGPT runner input for agent "${agent.name}" must include non-empty visible text.`);
|
|
309
|
+
}
|
|
310
|
+
return normalized;
|
|
311
|
+
}
|
|
312
|
+
function collectRunnerInput(input) {
|
|
313
|
+
if (typeof input === "string") {
|
|
314
|
+
return { prompt: input, files: [] };
|
|
315
|
+
}
|
|
316
|
+
const visibleInstructions = [];
|
|
317
|
+
const userText = [];
|
|
318
|
+
const files = [];
|
|
319
|
+
for (const item of input) {
|
|
320
|
+
switch (item.type) {
|
|
321
|
+
case "input_text":
|
|
322
|
+
userText.push(item.text);
|
|
323
|
+
break;
|
|
324
|
+
case "visible_instruction":
|
|
325
|
+
visibleInstructions.push(item.text);
|
|
326
|
+
break;
|
|
327
|
+
case "input_file":
|
|
328
|
+
files.push(item.path);
|
|
329
|
+
if (item.description !== undefined && item.description.trim().length > 0) {
|
|
330
|
+
userText.push(`Attached file context: ${item.description.trim()}`);
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const parts = [];
|
|
336
|
+
if (visibleInstructions.length > 0) {
|
|
337
|
+
parts.push(`<visible_instructions>\n${visibleInstructions.join("\n")}\n</visible_instructions>`);
|
|
338
|
+
}
|
|
339
|
+
if (userText.length > 0) {
|
|
340
|
+
parts.push(userText.join("\n\n"));
|
|
341
|
+
}
|
|
342
|
+
return { prompt: parts.join("\n\n"), files };
|
|
343
|
+
}
|
|
344
|
+
function normalizeRunnerAttachments(attachments) {
|
|
345
|
+
return (attachments ?? []).map(attachment => attachment.path);
|
|
346
|
+
}
|
|
347
|
+
function renderRunnerPrompt(agent, prompt) {
|
|
348
|
+
if (agent.instructionsMode !== "visible_prefix" || !hasInstructions(agent)) {
|
|
349
|
+
return prompt;
|
|
350
|
+
}
|
|
351
|
+
return `${renderAgentInstructionBlock(agent)}\n\n<user_request>\n${prompt}\n</user_request>`;
|
|
352
|
+
}
|
|
353
|
+
function renderAgentSetupMessage(agent) {
|
|
354
|
+
return `${renderAgentInstructionBlock(agent)}\n\nAcknowledge these visible setup instructions briefly, then wait for the next user request.`;
|
|
355
|
+
}
|
|
356
|
+
function renderAgentInstructionBlock(agent) {
|
|
357
|
+
return [
|
|
358
|
+
"<chatgpt_browser_agent>",
|
|
359
|
+
`Agent name: ${agent.name}`,
|
|
360
|
+
"Instructions:",
|
|
361
|
+
agent.instructions ?? "",
|
|
362
|
+
"</chatgpt_browser_agent>"
|
|
363
|
+
].join("\n");
|
|
364
|
+
}
|
|
365
|
+
function hasInstructions(agent) {
|
|
366
|
+
return (agent.instructions ?? "").trim().length > 0;
|
|
367
|
+
}
|
|
368
|
+
async function runPlanInvocation(plan, env, limits, defaults, reporting) {
|
|
369
|
+
try {
|
|
370
|
+
if (!("steps" in plan) && plan.name === "doctor-upload") {
|
|
371
|
+
const result = await doctor(env, { check: ["bridge", "login", "upload"] });
|
|
372
|
+
return maybeAttachReport(env, result, reportOptions(plan.report, reporting), limits);
|
|
373
|
+
}
|
|
374
|
+
if (!("steps" in plan) && plan.name === "redacted-run-report") {
|
|
375
|
+
const input = isRecord(plan.input) ? plan.input : {};
|
|
376
|
+
const result = input.result;
|
|
377
|
+
if (!isCommandResult(result)) {
|
|
378
|
+
throw new Error('Named workflow "redacted-run-report" requires input.result to be a CommandResult.');
|
|
379
|
+
}
|
|
380
|
+
return createRunReport(env, result, capReportOptions(reportOptions(plan.report, reporting) ?? {}, limits));
|
|
381
|
+
}
|
|
382
|
+
const resolved = "steps" in plan ? plan : resolvePlan(plan, defaults);
|
|
383
|
+
return runGuarded(resolved, env, limits, reportOptions("report" in plan ? plan.report : undefined, reporting));
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
return resultError(error instanceof Error ? error : new Error(String(error)), {});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function maybeAttachReport(env, result, report, limits) {
|
|
390
|
+
if (report === undefined || report.enabled === false)
|
|
391
|
+
return result;
|
|
392
|
+
const reportResult = await createRunReport(env, result, capReportOptions(report, limits));
|
|
393
|
+
if (!reportResult.ok || reportResult.data === undefined)
|
|
394
|
+
return result;
|
|
395
|
+
return { ...result, reportPath: reportResult.data.path };
|
|
396
|
+
}
|
|
397
|
+
function runtimeEnv(options) {
|
|
398
|
+
const env = {};
|
|
399
|
+
if (options.agent !== undefined)
|
|
400
|
+
env.agent = options.agent;
|
|
401
|
+
if (options.browser !== undefined)
|
|
402
|
+
env.browser = options.browser;
|
|
403
|
+
if (options.page !== undefined)
|
|
404
|
+
env.page = options.page;
|
|
405
|
+
if (options.clipboard !== undefined)
|
|
406
|
+
env.clipboard = options.clipboard;
|
|
407
|
+
if (options.now !== undefined)
|
|
408
|
+
env.now = options.now;
|
|
409
|
+
return env;
|
|
410
|
+
}
|
|
411
|
+
function planAskWorkflow(args, defaults = {}) {
|
|
412
|
+
const thread = args.thread ?? { type: "new" };
|
|
413
|
+
const steps = [
|
|
414
|
+
bootstrapStepForWorkflow(thread, args.existingTab ?? defaults.existingTab, args.preferExistingTab ?? defaults.preferExistingTab),
|
|
415
|
+
...threadSteps(thread)
|
|
416
|
+
];
|
|
417
|
+
const mode = args.mode ?? defaults.mode;
|
|
418
|
+
if (mode !== undefined) {
|
|
419
|
+
steps.push({ id: "mode", command: "modes.set", args: mode });
|
|
420
|
+
}
|
|
421
|
+
for (const [index, tool] of (args.tools ?? []).entries()) {
|
|
422
|
+
steps.push({ id: `tool${index + 1}`, command: "tools.select", args: tool });
|
|
423
|
+
}
|
|
424
|
+
const files = normalizeFileInputs([...(args.files ?? []), ...(args.attachments ?? [])]);
|
|
425
|
+
if (files.length > 0) {
|
|
426
|
+
steps.push({ id: "attach", command: "files.attach", args: { paths: files } });
|
|
427
|
+
}
|
|
428
|
+
steps.push({
|
|
429
|
+
id: "ask",
|
|
430
|
+
command: "messages.ask",
|
|
431
|
+
args: {
|
|
432
|
+
text: args.prompt,
|
|
433
|
+
wait: args.wait ?? defaults.wait ?? true,
|
|
434
|
+
read: args.read ?? defaults.read ?? { format: "markdown" }
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
if (args.download !== undefined) {
|
|
438
|
+
steps.push({ id: "download", command: "files.downloadLatest", args: args.download });
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
name: args.download === undefined ? "ask" : "ask-and-download",
|
|
442
|
+
policy: { stopOnError: true, returnPartial: true },
|
|
443
|
+
steps
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function planRunMessages(args, defaults = {}) {
|
|
447
|
+
const thread = args.thread ?? { type: "new" };
|
|
448
|
+
const steps = [
|
|
449
|
+
bootstrapStepForWorkflow(thread, args.existingTab ?? defaults.existingTab, args.preferExistingTab ?? defaults.preferExistingTab),
|
|
450
|
+
...threadSteps(thread)
|
|
451
|
+
];
|
|
452
|
+
const mode = args.mode ?? defaults.mode;
|
|
453
|
+
if (mode !== undefined) {
|
|
454
|
+
steps.push({ id: "mode", command: "modes.set", args: mode });
|
|
455
|
+
}
|
|
456
|
+
args.messages.forEach((message, index) => {
|
|
457
|
+
steps.push({
|
|
458
|
+
id: message.id ?? `message${index + 1}`,
|
|
459
|
+
command: "messages.ask",
|
|
460
|
+
args: {
|
|
461
|
+
text: message.prompt,
|
|
462
|
+
wait: message.wait ?? defaults.wait ?? true,
|
|
463
|
+
read: message.read ?? defaults.read ?? { format: "markdown" }
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
return { name: "run-messages", policy: { stopOnError: true, returnPartial: true }, steps };
|
|
468
|
+
}
|
|
469
|
+
function planOpenThread(thread) {
|
|
470
|
+
return {
|
|
471
|
+
name: "open-thread",
|
|
472
|
+
policy: { stopOnError: true, returnPartial: true },
|
|
473
|
+
steps: [
|
|
474
|
+
{ id: "bootstrap", command: "session.bootstrap" },
|
|
475
|
+
...threadSteps(thread)
|
|
476
|
+
]
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function planByName(name, args, defaults = {}) {
|
|
480
|
+
const input = isRecord(args) ? args : {};
|
|
481
|
+
switch (name) {
|
|
482
|
+
case "new-ask-read":
|
|
483
|
+
return planAskWorkflow({ prompt: stringInput(input, "prompt"), thread: { type: "new" } }, defaults);
|
|
484
|
+
case "find-open-copy-latest":
|
|
485
|
+
return {
|
|
486
|
+
name,
|
|
487
|
+
steps: [
|
|
488
|
+
{ id: "bootstrap", command: "session.bootstrap" },
|
|
489
|
+
{ id: "find", command: "threads.search", args: { query: stringInput(input, "query"), limit: 5 } },
|
|
490
|
+
{ id: "open", command: "threads.open", args: { fromStep: "find", select: "first" } },
|
|
491
|
+
{ id: "copy", command: "response.copy", args: { which: "latest" } }
|
|
492
|
+
]
|
|
493
|
+
};
|
|
494
|
+
case "find-open-ask-read":
|
|
495
|
+
return planAskWorkflow({
|
|
496
|
+
prompt: stringInput(input, "prompt"),
|
|
497
|
+
thread: { type: "search", query: stringInput(input, "query"), select: "first" }
|
|
498
|
+
}, defaults);
|
|
499
|
+
case "attach-ask-read":
|
|
500
|
+
return planAskWorkflow({
|
|
501
|
+
prompt: stringInput(input, "prompt"),
|
|
502
|
+
thread: { type: "new" },
|
|
503
|
+
files: arrayInput(input, "files").map(String)
|
|
504
|
+
}, defaults);
|
|
505
|
+
case "ask-and-download":
|
|
506
|
+
return planAskWorkflow({
|
|
507
|
+
prompt: stringInput(input, "prompt"),
|
|
508
|
+
thread: { type: "new" },
|
|
509
|
+
download: { destDir: stringInput(input, "destDir") }
|
|
510
|
+
}, defaults);
|
|
511
|
+
case "two-turn":
|
|
512
|
+
return planRunMessages({
|
|
513
|
+
thread: { type: "new" },
|
|
514
|
+
messages: [
|
|
515
|
+
{ id: "first", prompt: stringInput(input, "first") },
|
|
516
|
+
{ id: "second", prompt: stringInput(input, "second") }
|
|
517
|
+
]
|
|
518
|
+
}, defaults);
|
|
519
|
+
default:
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function resolvePlan(plan, defaults = {}) {
|
|
524
|
+
if ("steps" in plan)
|
|
525
|
+
return plan;
|
|
526
|
+
const resolved = planByName(plan.name, plan.input, defaults);
|
|
527
|
+
if (resolved === undefined) {
|
|
528
|
+
throw new Error(`Unknown ChatGPT workflow plan: ${plan.name}`);
|
|
529
|
+
}
|
|
530
|
+
return resolved;
|
|
531
|
+
}
|
|
532
|
+
function resultSummary(result) {
|
|
533
|
+
return {
|
|
534
|
+
ok: result.ok,
|
|
535
|
+
status: result.status,
|
|
536
|
+
warnings: result.warnings,
|
|
537
|
+
blocker: result.blocker,
|
|
538
|
+
error: result.error,
|
|
539
|
+
context: result.context,
|
|
540
|
+
reportPath: result.reportPath
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function isCommandResult(value) {
|
|
544
|
+
return isRecord(value)
|
|
545
|
+
&& typeof value.ok === "boolean"
|
|
546
|
+
&& typeof value.status === "string"
|
|
547
|
+
&& Array.isArray(value.warnings)
|
|
548
|
+
&& isRecord(value.context)
|
|
549
|
+
&& typeof value.context.timestamp === "string";
|
|
550
|
+
}
|
|
551
|
+
function bootstrapStepForWorkflow(thread, existingTab, preferExistingTab) {
|
|
552
|
+
const args = bootstrapArgsForWorkflow(thread, existingTab, preferExistingTab);
|
|
553
|
+
if (args === undefined) {
|
|
554
|
+
return { id: "bootstrap", command: "session.bootstrap" };
|
|
555
|
+
}
|
|
556
|
+
return { id: "bootstrap", command: "session.bootstrap", args };
|
|
557
|
+
}
|
|
558
|
+
function bootstrapArgsForWorkflow(thread, existingTab, preferExistingTab) {
|
|
559
|
+
const args = {};
|
|
560
|
+
if (existingTab !== undefined) {
|
|
561
|
+
args.existingTab = existingTab === true ? existingTabPolicyFromThread(thread) : existingTab;
|
|
562
|
+
}
|
|
563
|
+
if (preferExistingTab !== undefined) {
|
|
564
|
+
args.preferExistingTab = preferExistingTab;
|
|
565
|
+
}
|
|
566
|
+
return Object.keys(args).length === 0 ? undefined : args;
|
|
567
|
+
}
|
|
568
|
+
function existingTabPolicyFromThread(thread) {
|
|
569
|
+
const target = existingTabTargetFromThread(thread);
|
|
570
|
+
if (target === undefined) {
|
|
571
|
+
return {
|
|
572
|
+
target: { type: "selected", host: "chatgpt" },
|
|
573
|
+
ifMissing: "block",
|
|
574
|
+
ifMultiple: "first",
|
|
575
|
+
requireChatGPT: true
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
target,
|
|
580
|
+
ifMissing: "block",
|
|
581
|
+
ifMultiple: target.type === "selected" ? "first" : "block",
|
|
582
|
+
requireChatGPT: true
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function existingTabTargetFromThread(thread) {
|
|
586
|
+
if (isTypedThread(thread)) {
|
|
587
|
+
switch (thread.type) {
|
|
588
|
+
case "new":
|
|
589
|
+
case "search":
|
|
590
|
+
return undefined;
|
|
591
|
+
case "current":
|
|
592
|
+
return { type: "selected", host: "chatgpt" };
|
|
593
|
+
case "url":
|
|
594
|
+
return { type: "url", url: thread.url };
|
|
595
|
+
case "conversationId":
|
|
596
|
+
case "conversation_id":
|
|
597
|
+
return { type: "conversationId", conversationId: thread.conversationId };
|
|
598
|
+
case "title":
|
|
599
|
+
return { type: "title", title: thread.title, exact: false };
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (thread.url !== undefined)
|
|
603
|
+
return { type: "url", url: thread.url };
|
|
604
|
+
if (thread.conversationId !== undefined)
|
|
605
|
+
return { type: "conversationId", conversationId: thread.conversationId };
|
|
606
|
+
if (thread.title !== undefined)
|
|
607
|
+
return { type: "title", title: thread.title, exact: false };
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
function threadSteps(thread) {
|
|
611
|
+
if (isTypedThread(thread)) {
|
|
612
|
+
switch (thread.type) {
|
|
613
|
+
case "new":
|
|
614
|
+
return [{ id: "new", command: "threads.new" }];
|
|
615
|
+
case "current":
|
|
616
|
+
return [];
|
|
617
|
+
case "url":
|
|
618
|
+
return [{ id: "open", command: "threads.open", args: { url: thread.url } }];
|
|
619
|
+
case "conversationId":
|
|
620
|
+
return [{ id: "open", command: "threads.open", args: { conversationId: thread.conversationId } }];
|
|
621
|
+
case "conversation_id":
|
|
622
|
+
return [{ id: "open", command: "threads.open", args: { conversationId: thread.conversationId } }];
|
|
623
|
+
case "search":
|
|
624
|
+
return [
|
|
625
|
+
{ id: "find", command: "threads.search", args: { query: thread.query, limit: thread.limit ?? 5 } },
|
|
626
|
+
{ id: "open", command: "threads.open", args: { fromStep: "find", select: thread.select ?? "first" } }
|
|
627
|
+
];
|
|
628
|
+
case "title":
|
|
629
|
+
return [{ id: "open", command: "threads.open", args: { title: thread.title } }];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (thread.url !== undefined)
|
|
633
|
+
return [{ id: "open", command: "threads.open", args: { url: thread.url } }];
|
|
634
|
+
if (thread.conversationId !== undefined)
|
|
635
|
+
return [{ id: "open", command: "threads.open", args: { conversationId: thread.conversationId } }];
|
|
636
|
+
const query = thread.query ?? thread.title;
|
|
637
|
+
if (query === undefined)
|
|
638
|
+
return [];
|
|
639
|
+
return [
|
|
640
|
+
{ id: "find", command: "threads.search", args: { query, limit: 5 } },
|
|
641
|
+
{ id: "open", command: "threads.open", args: { fromStep: "find", select: thread.title === undefined ? "first" : { title: thread.title } } }
|
|
642
|
+
];
|
|
643
|
+
}
|
|
644
|
+
function isTypedThread(thread) {
|
|
645
|
+
return "type" in thread;
|
|
646
|
+
}
|
|
647
|
+
function normalizeFileInputs(files) {
|
|
648
|
+
return files.map(file => typeof file === "string" ? file : file.path);
|
|
649
|
+
}
|
|
650
|
+
function isRecord(value) {
|
|
651
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
652
|
+
}
|
|
653
|
+
function stringInput(input, key) {
|
|
654
|
+
const value = input[key];
|
|
655
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
656
|
+
throw new Error(`Named workflow input "${key}" must be a non-empty string.`);
|
|
657
|
+
}
|
|
658
|
+
return value;
|
|
659
|
+
}
|
|
660
|
+
function arrayInput(input, key) {
|
|
661
|
+
const value = input[key];
|
|
662
|
+
if (!Array.isArray(value)) {
|
|
663
|
+
throw new Error(`Named workflow input "${key}" must be an array.`);
|
|
664
|
+
}
|
|
665
|
+
return value;
|
|
666
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CommandResult } from "../types.js";
|
|
2
|
+
export type Confirmation = {
|
|
3
|
+
targetKind: string;
|
|
4
|
+
targetDisplayName: string;
|
|
5
|
+
action: string;
|
|
6
|
+
understood: true;
|
|
7
|
+
};
|
|
8
|
+
export declare function requireConfirmation<T>(confirm: Confirmation | undefined, expected: Omit<Confirmation, "understood">): CommandResult<T> | undefined;
|
|
9
|
+
export declare function rejectNetworkCommand<T>(command: string): CommandResult<T> | undefined;
|