@zq-silk/yui 2.21.0 → 2.23.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 +5 -0
- package/dist/cli/commandCatalog.js +87 -27
- package/dist/cli/dynamicCompletion.js +4 -1
- package/dist/cli/helpRenderer.js +12 -1
- package/dist/cli/interactionPolicy.js +2 -0
- package/dist/cli/invocationRouter.js +5 -5
- package/dist/cli/roleWizard.js +5 -1
- package/dist/cli/updateCommand.js +20 -3
- package/dist/cli/updateOrchestrator.js +13 -0
- package/dist/cli/updatePorts.js +27 -0
- package/dist/commands/globalRoleCommands.js +33 -8
- package/dist/commands/inboxCommands.js +39 -12
- package/dist/commands/operatorCommands.js +2 -5
- package/dist/commands/releaseCommands.js +16 -2
- package/dist/commands/roleConfiguration.js +25 -2
- package/dist/commands/sessionCommands.js +5 -2
- package/dist/commands/taskCommands.js +217 -64
- package/dist/commands/taskContextCommand.js +26 -4
- package/dist/commands/taskFactCommands.js +15 -2
- package/dist/commands/taskIntegrationCommands.js +3 -3
- package/dist/commands/taskRoleRuntimeStatus.js +19 -5
- package/dist/commands/textInput.js +2 -2
- package/dist/context/contextFamilies.js +10 -0
- package/dist/context/currentSkills.js +37 -4
- package/dist/context/runContextPack.js +44 -5
- package/dist/context/runInputContract.js +1 -1
- package/dist/context/sessionBootstrapManifest.js +21 -6
- package/dist/context/sessionSkillNotice.js +74 -0
- package/dist/context/taskContext.js +100 -16
- package/dist/controlPlaneCli.js +17 -7
- package/dist/controller/agentRuntimeObserver.js +57 -12
- package/dist/controller/clientRuntime.js +66 -18
- package/dist/controller/controller.js +19 -2
- package/dist/controller/diagnosticJournal.js +10 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +74 -24
- package/dist/controller/hostLifecycle.js +153 -0
- package/dist/controller/hostLifecycleChecks.js +27 -0
- package/dist/controller/hostUpgrade.js +344 -0
- package/dist/controller/messageHealth.js +4 -3
- package/dist/controller/providerRetryAdmission.js +15 -1
- package/dist/controller/providerRetryDelivery.js +18 -2
- package/dist/controller/runtime.js +82 -21
- package/dist/controller/runtimeEventInbox.js +107 -13
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/controller/sessionNotify.js +5 -46
- package/dist/controller/taskRunRecovery.js +153 -0
- package/dist/core/controllerClient.js +16 -2
- package/dist/core/controllerServer.js +14 -5
- package/dist/core/controllerTelemetry.js +51 -5
- package/dist/errors/cliFailure.js +14 -5
- package/dist/execution/codexThreadNaming.js +54 -43
- package/dist/executor/agentAdapter.js +32 -4
- package/dist/executor/agentConfigurationFields.js +2 -0
- package/dist/executor/fileRoleLaunchPlanner.js +18 -3
- package/dist/integration/gitIntegrationService.js +3 -2
- package/dist/integration/integrationSourceApplication.js +12 -1
- package/dist/interaction/notificationPresentation.js +11 -16
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/message/messageContinuation.js +3 -1
- package/dist/output/boundedRead.js +36 -7
- package/dist/output/rolePresentation.js +3 -1
- package/dist/runtime/agentEndpoint.js +11 -1
- package/dist/runtime/agentEndpointIdentity.js +2 -0
- package/dist/runtime/agentHost.js +174 -40
- package/dist/runtime/agentHostImplementation.js +3 -1
- package/dist/runtime/codexAppServerRuntime.js +113 -24
- package/dist/runtime/codexInteractiveHost.js +102 -11
- package/dist/runtime/codexNativeAccess.js +1 -1
- package/dist/runtime/contextRecovery.js +15 -0
- package/dist/runtime/hostSessionLifecycle.js +83 -0
- package/dist/runtime/hostUpgradeGate.js +43 -0
- package/dist/runtime/lifecycleChecks.js +47 -0
- package/dist/runtime/lifecycleReservation.js +9 -1
- package/dist/runtime/nativeTitleReceipt.js +31 -0
- package/dist/runtime/nativeTitleSync.js +192 -0
- package/dist/runtime/providerRetry.js +9 -6
- package/dist/runtime/providerRuntimeIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +65 -35
- package/dist/scheduler/wakeupQueue.js +10 -4
- package/dist/storage/sqliteSchema.js +29 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/tmux/tmuxManager.js +4 -2
- package/dist/web/webSettings.js +2 -0
- package/docs/authorization.md +18 -3
- package/docs/cli-information-contract.md +228 -6
- package/docs/cli-information-contract.zh-CN.md +95 -5
- package/docs/managed-turn-and-session-runtime.md +177 -21
- package/docs/managed-turn-and-session-runtime.zh-CN.md +126 -14
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +65 -0
- package/docs/release-workflow.zh-CN.md +48 -0
- package/docs/roles-and-configuration.md +28 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/storage-baseline.md +13 -1
- package/docs/storage-baseline.zh-CN.md +5 -1
- package/docs/task-delivery.md +7 -0
- package/docs/task-delivery.zh-CN.md +5 -0
- package/docs/testing/review-evaluation.md +7 -7
- package/docs/testing/review-evaluation.zh-CN.md +7 -7
- package/docs/testing/verification-levels.md +45 -2
- package/docs/testing/verification-levels.zh-CN.md +21 -2
- package/i18n/README.zh-CN.md +6 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +98 -193
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/artifacts.md +41 -0
- package/skills/yui-leader/references/assignment-execution.md +140 -0
- package/skills/yui-leader/references/completion.md +79 -0
- package/skills/yui-leader/references/delivery-scope.md +26 -0
- package/skills/yui-leader/references/execution.md +16 -487
- package/skills/yui-leader/references/implementation.md +81 -0
- package/skills/yui-leader/references/planning.md +7 -45
- package/skills/yui-leader/references/review.md +90 -0
- package/skills/yui-leader/references/task-progress.md +23 -0
- package/skills/yui-leader/references/work-allocation.md +118 -0
- package/skills/yui-operator/SKILL.md +77 -333
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-operator/references/body-transport.md +62 -0
- package/skills/yui-operator/references/configuration.md +47 -0
- package/skills/yui-operator/references/lifecycle.md +41 -0
- package/skills/yui-operator/references/progress.md +39 -0
- package/skills/yui-operator/references/routing.md +57 -0
- package/skills/yui-operator/references/submission.md +56 -0
- package/skills/yui-operator/references/task-delivery.md +1 -1
- package/skills/yui-operator/references/web-lifecycle.md +19 -0
- package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
- package/skills/yui-runtime/SKILL.md +182 -531
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/archive.md +51 -0
- package/skills/yui-runtime/references/command-discovery.md +41 -0
- package/skills/yui-runtime/references/completed-followup.md +65 -0
- package/skills/yui-runtime/references/context-ordering.md +3 -34
- package/skills/yui-runtime/references/context-reads.md +61 -0
- package/skills/yui-runtime/references/dispatched-run.md +61 -0
- package/skills/yui-runtime/references/execution-failure.md +105 -0
- package/skills/yui-runtime/references/global-context.md +18 -0
- package/skills/yui-runtime/references/global-input-controls.md +44 -0
- package/skills/yui-runtime/references/host-custody.md +30 -0
- package/skills/yui-runtime/references/inbox-recovery.md +77 -0
- package/skills/yui-runtime/references/incremental-context.md +40 -0
- package/skills/yui-runtime/references/publication.md +4 -111
- package/skills/yui-runtime/references/recovery.md +32 -192
- package/skills/yui-runtime/references/session-identity.md +18 -0
- package/skills/yui-runtime/references/session-replacement.md +45 -0
- package/skills/yui-runtime/references/uncertain-execution.md +29 -0
|
@@ -79,8 +79,17 @@ export function describeCliFailure(error, context = {}) {
|
|
|
79
79
|
? "The Controller returned an error response; it does not prove rollback of earlier effects. " + RECONCILE
|
|
80
80
|
: RECONCILE;
|
|
81
81
|
const help = error instanceof CliError && error.helpText !== undefined ? clean(error.helpText.trimEnd()) : undefined;
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
const causes = chain.map((item, index) => `${index === 0 ? "" : "Caused by: "}${item.code === undefined ? "" : `[${item.code}] `}${item.message}`);
|
|
83
|
+
const lines = (parts) => parts.filter((part) => part !== undefined && part.length > 0).join("\n");
|
|
84
|
+
const jsonMessage = lines([
|
|
85
|
+
causes[0],
|
|
86
|
+
effects,
|
|
87
|
+
guidance,
|
|
88
|
+
"Use the same Home and CLI in details.diagnostic; its causes retain the full error chain. Recovery commands are not run automatically.",
|
|
89
|
+
help
|
|
90
|
+
]);
|
|
91
|
+
const message = lines([
|
|
92
|
+
...causes,
|
|
84
93
|
diagnostic.operation === undefined ? undefined : `Operation: ${diagnostic.operation}`,
|
|
85
94
|
diagnostic.home === undefined ? undefined : `YUI_HOME: ${diagnostic.home}`,
|
|
86
95
|
diagnostic.cli === undefined ? undefined : `CLI entry: ${diagnostic.cli}`,
|
|
@@ -92,9 +101,9 @@ export function describeCliFailure(error, context = {}) {
|
|
|
92
101
|
guidance,
|
|
93
102
|
"Recovery commands shown as `yui` must use the same Home and matching CLI entry above; they are not run automatically.",
|
|
94
103
|
help
|
|
95
|
-
]
|
|
104
|
+
]);
|
|
96
105
|
return {
|
|
97
|
-
code, exitCode, message,
|
|
106
|
+
code, exitCode, message, jsonMessage,
|
|
98
107
|
details: {
|
|
99
108
|
...(error instanceof CliError ? sanitizeFailureDetails(error.details, context) : {}),
|
|
100
109
|
diagnostic
|
|
@@ -103,7 +112,7 @@ export function describeCliFailure(error, context = {}) {
|
|
|
103
112
|
}
|
|
104
113
|
export function renderCliFailure(failure, json, jsonCode = failure.code) {
|
|
105
114
|
return json
|
|
106
|
-
? JSON.stringify({ ok: false, code: jsonCode, message: failure.
|
|
115
|
+
? JSON.stringify({ ok: false, code: jsonCode, message: failure.jsonMessage, details: failure.details })
|
|
107
116
|
: `${failure.code}: ${failure.message}`;
|
|
108
117
|
}
|
|
109
118
|
function errorCode(error) {
|
|
@@ -2,52 +2,64 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { codexClientInitialization } from "../runtime/codexAppServerRuntime.js";
|
|
4
4
|
const DEFAULT_TIMEOUT_MS = 1_000;
|
|
5
|
-
export
|
|
6
|
-
const timeoutMs = threadNameTimeout(request.timeoutMs);
|
|
7
|
-
const client = await CodexNamingClient.start(request.command, request.environment, request.baseArgs ?? [], timeoutMs);
|
|
8
|
-
try {
|
|
9
|
-
await withTimeout(client.request("thread/name/set", {
|
|
10
|
-
threadId: requiredText(request.threadId, "Codex thread id"),
|
|
11
|
-
name: requiredThreadName(request.name)
|
|
12
|
-
}), timeoutMs, "Timed out setting Codex thread name.");
|
|
13
|
-
}
|
|
14
|
-
finally {
|
|
15
|
-
client.close();
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
class CodexNamingClient {
|
|
5
|
+
export class CodexNamingClient {
|
|
19
6
|
child;
|
|
7
|
+
accountHome;
|
|
8
|
+
exited;
|
|
20
9
|
#pending = new Map();
|
|
21
10
|
#stderr = [];
|
|
22
11
|
#nextId = 1;
|
|
23
12
|
#closed = false;
|
|
24
|
-
|
|
13
|
+
#deadline;
|
|
14
|
+
constructor(child, timeoutMs) {
|
|
25
15
|
this.child = child;
|
|
16
|
+
this.exited = new Promise(resolve => child.once("close", () => resolve()));
|
|
17
|
+
this.#deadline = setTimeout(() => {
|
|
18
|
+
this.#failAll(new Error("Native title metadata process deadline exceeded; effect may be unknown."));
|
|
19
|
+
this.close("SIGKILL");
|
|
20
|
+
}, timeoutMs);
|
|
26
21
|
const output = createInterface({ input: child.stdout });
|
|
27
|
-
output.on("line", (line) => {
|
|
22
|
+
output.on("line", (line) => {
|
|
23
|
+
try {
|
|
24
|
+
if (Buffer.byteLength(line) > 1_048_576)
|
|
25
|
+
throw new Error("Native title reply exceeds its bound.");
|
|
26
|
+
this.#receive(line);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
this.#failAll(error instanceof Error ? error : new Error(String(error)));
|
|
30
|
+
this.close();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
child.stdout.on("error", error => { this.#failAll(error); this.close(); });
|
|
28
34
|
child.stderr.setEncoding("utf8");
|
|
29
35
|
child.stderr.on("data", (chunk) => {
|
|
30
36
|
this.#stderr.push(chunk);
|
|
31
37
|
if (this.#stderr.join("").length > 16_384)
|
|
32
38
|
this.#stderr.shift();
|
|
33
39
|
});
|
|
34
|
-
child.on("error", (error) => { this.#failAll(error); });
|
|
40
|
+
child.on("error", (error) => { this.#failAll(error); this.close(); });
|
|
41
|
+
child.stdin.on("error", (error) => { this.#failAll(error); this.close(); });
|
|
35
42
|
child.on("exit", (code, signal) => {
|
|
43
|
+
clearTimeout(this.#deadline);
|
|
36
44
|
if (this.#closed)
|
|
37
45
|
return;
|
|
46
|
+
this.#closed = true;
|
|
38
47
|
const detail = this.#stderr.join("").trim();
|
|
39
48
|
this.#failAll(new Error(`Codex App Server exited (${code ?? signal ?? "unknown"})${detail.length === 0 ? "" : `: ${detail}`}`));
|
|
40
49
|
});
|
|
41
50
|
}
|
|
42
51
|
static async start(command, environment, baseArgs, timeoutMs) {
|
|
43
|
-
const
|
|
52
|
+
const deadlineMs = threadNameTimeout(timeoutMs);
|
|
53
|
+
const client = new CodexNamingClient(spawn(command, [...baseArgs, "app-server", "--listen", "stdio://"], { env: environment, stdio: ["pipe", "pipe", "pipe"] }), deadlineMs);
|
|
44
54
|
try {
|
|
45
|
-
await withTimeout(client.request("initialize", codexClientInitialization()), timeoutMs, "Timed out initializing Codex App Server for thread naming.");
|
|
55
|
+
const initialized = await withTimeout(client.request("initialize", codexClientInitialization()), timeoutMs, "Timed out initializing Codex App Server for thread naming.");
|
|
56
|
+
client.accountHome = typeof initialized.codexHome === "string" ? initialized.codexHome : undefined;
|
|
46
57
|
client.notify("initialized", {});
|
|
47
58
|
return client;
|
|
48
59
|
}
|
|
49
60
|
catch (error) {
|
|
50
61
|
client.close();
|
|
62
|
+
await client.exited;
|
|
51
63
|
throw error;
|
|
52
64
|
}
|
|
53
65
|
}
|
|
@@ -63,11 +75,19 @@ class CodexNamingClient {
|
|
|
63
75
|
notify(method, params) {
|
|
64
76
|
this.#write({ method, params });
|
|
65
77
|
}
|
|
66
|
-
close() {
|
|
78
|
+
close(signal = "SIGTERM") {
|
|
67
79
|
if (this.#closed)
|
|
68
80
|
return;
|
|
69
81
|
this.#closed = true;
|
|
70
|
-
this
|
|
82
|
+
clearTimeout(this.#deadline);
|
|
83
|
+
this.child.kill(signal);
|
|
84
|
+
// An owned metadata process must not survive a lost reply or ignored TERM.
|
|
85
|
+
const reap = setTimeout(() => {
|
|
86
|
+
if (this.child.exitCode === null && this.child.signalCode === null)
|
|
87
|
+
this.child.kill("SIGKILL");
|
|
88
|
+
}, 250);
|
|
89
|
+
reap.unref();
|
|
90
|
+
this.child.once("exit", () => clearTimeout(reap));
|
|
71
91
|
this.#failAll(new Error("Codex App Server closed."));
|
|
72
92
|
}
|
|
73
93
|
#write(value) {
|
|
@@ -94,13 +114,20 @@ class CodexNamingClient {
|
|
|
94
114
|
}
|
|
95
115
|
return;
|
|
96
116
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
try {
|
|
118
|
+
if (message.error !== undefined) {
|
|
119
|
+
const error = object(message.error, "Codex App Server error");
|
|
120
|
+
pending.reject(new Error(typeof error.message === "string" ? error.message : "Codex App Server request failed."));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
pending.resolve(object(message.result, "Codex App Server result"));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
101
128
|
}
|
|
102
|
-
|
|
103
|
-
pending.
|
|
129
|
+
finally {
|
|
130
|
+
this.#pending.delete(message.id);
|
|
104
131
|
}
|
|
105
132
|
}
|
|
106
133
|
#failAll(error) {
|
|
@@ -115,22 +142,6 @@ function object(value, label) {
|
|
|
115
142
|
}
|
|
116
143
|
return value;
|
|
117
144
|
}
|
|
118
|
-
function requiredText(value, label) {
|
|
119
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
120
|
-
throw new Error(`${label} is invalid.`);
|
|
121
|
-
}
|
|
122
|
-
return value;
|
|
123
|
-
}
|
|
124
|
-
function requiredThreadName(value) {
|
|
125
|
-
if (typeof value !== "string" || value.includes("\0")) {
|
|
126
|
-
throw new Error("Codex thread name is invalid.");
|
|
127
|
-
}
|
|
128
|
-
const normalized = value.trim();
|
|
129
|
-
if (normalized.length === 0 || normalized.length > 1_024) {
|
|
130
|
-
throw new Error("Codex thread name is invalid.");
|
|
131
|
-
}
|
|
132
|
-
return normalized;
|
|
133
|
-
}
|
|
134
145
|
function threadNameTimeout(value) {
|
|
135
146
|
if (value === undefined)
|
|
136
147
|
return DEFAULT_TIMEOUT_MS;
|
|
@@ -87,7 +87,7 @@ class CodexAdapter extends BaseAdapter {
|
|
|
87
87
|
throw new Error("Codex permission strategy is required.");
|
|
88
88
|
}
|
|
89
89
|
if (config.permission.strategy === "configured") {
|
|
90
|
-
exact(config.permission, ["strategy", "sandbox", "approval"], "Codex permission config");
|
|
90
|
+
exact(config.permission, ["strategy", "sandbox", "approval", "excludeTmpdirEnvVar", "excludeSlashTmp"], "Codex permission config");
|
|
91
91
|
if (config.permission.sandbox !== undefined
|
|
92
92
|
&& !SANDBOXES.includes(config.permission.sandbox)) {
|
|
93
93
|
throw new Error("Codex sandbox is invalid.");
|
|
@@ -96,6 +96,16 @@ class CodexAdapter extends BaseAdapter {
|
|
|
96
96
|
&& !APPROVALS.includes(config.permission.approval)) {
|
|
97
97
|
throw new Error("Codex approval is invalid.");
|
|
98
98
|
}
|
|
99
|
+
for (const key of ["excludeTmpdirEnvVar", "excludeSlashTmp"]) {
|
|
100
|
+
const value = config.permission[key];
|
|
101
|
+
if (value === undefined)
|
|
102
|
+
continue;
|
|
103
|
+
if (typeof value !== "boolean")
|
|
104
|
+
throw new Error(`Codex ${key} must be boolean.`);
|
|
105
|
+
if (config.permission.sandbox !== "workspace-write") {
|
|
106
|
+
throw new Error(`Codex ${key} requires an explicit workspace-write sandbox.`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
99
109
|
requireConfiguredPermissionOption(config.permission, "Codex");
|
|
100
110
|
}
|
|
101
111
|
else {
|
|
@@ -118,7 +128,8 @@ class CodexAdapter extends BaseAdapter {
|
|
|
118
128
|
...(config.permission.sandbox === undefined
|
|
119
129
|
? [] : ["--sandbox", config.permission.sandbox]),
|
|
120
130
|
...(config.permission.approval === undefined
|
|
121
|
-
? [] : ["--ask-for-approval", config.permission.approval])
|
|
131
|
+
? [] : ["--ask-for-approval", config.permission.approval]),
|
|
132
|
+
...Object.entries(codexWorkspaceWriteOptions(config)).flatMap(([key, value]) => ["--config", `sandbox_workspace_write.${key}=${value}`])
|
|
122
133
|
]
|
|
123
134
|
: []),
|
|
124
135
|
...(config.search === true ? ["--search"] : []),
|
|
@@ -158,7 +169,12 @@ class CodexAdapter extends BaseAdapter {
|
|
|
158
169
|
// A managed Codex thread is an ordinary user thread. Its Yui guidance is
|
|
159
170
|
// part of the durable Task message, not a developer_instructions override
|
|
160
171
|
// that would shadow the user's native project/developer configuration.
|
|
161
|
-
|
|
172
|
+
let argv = withoutCodexConfigOverride(launch.argv, "developer_instructions");
|
|
173
|
+
// Managed exclusions belong to thread/start and thread/resume configuration,
|
|
174
|
+
// never the shared daemon/proxy startup defaults.
|
|
175
|
+
for (const key of Object.keys(codexWorkspaceWriteOptions(config))) {
|
|
176
|
+
argv = withoutCodexConfigOverride(argv, `sandbox_workspace_write.${key}`);
|
|
177
|
+
}
|
|
162
178
|
if (config.model !== undefined) {
|
|
163
179
|
const modelFlag = argv.findIndex((value, index) => (value === "--model" && argv[index + 1] === config.model));
|
|
164
180
|
if (modelFlag < 0)
|
|
@@ -475,7 +491,10 @@ function codexThreadOptions(input, config) {
|
|
|
475
491
|
// Keep the trust override on this thread instead of mutating global config.
|
|
476
492
|
projects: { [resolve(input.workspace)]: { trust_level: "trusted" } },
|
|
477
493
|
...(config.effort === undefined ? {} : { model_reasoning_effort: config.effort }),
|
|
478
|
-
...(config.search === true ? { web_search: "live" } : {})
|
|
494
|
+
...(config.search === true ? { web_search: "live" } : {}),
|
|
495
|
+
...(Object.keys(codexWorkspaceWriteOptions(config)).length === 0 ? {} : {
|
|
496
|
+
sandbox_workspace_write: codexWorkspaceWriteOptions(config)
|
|
497
|
+
})
|
|
479
498
|
};
|
|
480
499
|
const permission = config.permission.strategy === "bypass"
|
|
481
500
|
? { approvalPolicy: "never", sandbox: "danger-full-access" }
|
|
@@ -496,6 +515,15 @@ function codexThreadOptions(input, config) {
|
|
|
496
515
|
...(Object.keys(threadConfig).length === 0 ? {} : { config: threadConfig })
|
|
497
516
|
};
|
|
498
517
|
}
|
|
518
|
+
function codexWorkspaceWriteOptions(config) {
|
|
519
|
+
if (config.permission.strategy !== "configured")
|
|
520
|
+
return {};
|
|
521
|
+
const { excludeTmpdirEnvVar, excludeSlashTmp } = config.permission;
|
|
522
|
+
return {
|
|
523
|
+
...(excludeTmpdirEnvVar === undefined ? {} : { exclude_tmpdir_env_var: excludeTmpdirEnvVar }),
|
|
524
|
+
...(excludeSlashTmp === undefined ? {} : { exclude_slash_tmp: excludeSlashTmp })
|
|
525
|
+
};
|
|
526
|
+
}
|
|
499
527
|
function codexSessionInstructions(input) {
|
|
500
528
|
if (input.sessionManifestPath !== undefined) {
|
|
501
529
|
return sessionContextInstructions(input.sessionManifestPath, input.sessionManifestDigest);
|
|
@@ -15,6 +15,8 @@ export function staticAgentConfigurationFields(id) {
|
|
|
15
15
|
...fields,
|
|
16
16
|
staticField("permission.sandbox", CODEX_SANDBOXES, false),
|
|
17
17
|
staticField("permission.approval", CODEX_APPROVALS, false, "Native versions may retire values accepted by the adapter."),
|
|
18
|
+
staticField("permission.excludeTmpdirEnvVar", ["true", "false"], false, "Requires explicit workspace-write; omission follows the native default."),
|
|
19
|
+
staticField("permission.excludeSlashTmp", ["true", "false"], false, "Requires explicit workspace-write; omission follows the native default."),
|
|
18
20
|
staticField("search", ["true"], false),
|
|
19
21
|
staticField("profile", [], true),
|
|
20
22
|
staticField("additionalDirectories", [], true)
|
|
@@ -17,7 +17,7 @@ import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
|
|
|
17
17
|
import { builtinAgentEndpointImplementation, validateAgentEndpointImplementation } from "../runtime/agentEndpointIdentity.js";
|
|
18
18
|
import { builtinAgentDriverRegistry, builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
19
19
|
import { assertProviderConversationReplaceable } from "../runtime/providerRuntimeIdentity.js";
|
|
20
|
-
import { globalRoleSessionTitle, resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
20
|
+
import { globalRoleSessionTitle, taskRoleSessionTitle, resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
21
21
|
import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment } from "../runtime/taskRuntimeIsolation.js";
|
|
22
22
|
import { planningRuntimeCwd } from "../storage/homeLayout.js";
|
|
23
23
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
@@ -225,7 +225,11 @@ export class FileRoleLaunchPlanner {
|
|
|
225
225
|
instanceCode: role.instanceCode,
|
|
226
226
|
generatedTitle: existing?.generatedTitle
|
|
227
227
|
}), input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, effective, {
|
|
228
|
-
purpose
|
|
228
|
+
purpose,
|
|
229
|
+
nativeGeneratedTitle: taskRoleSessionTitle(task, role.name, {
|
|
230
|
+
executionAuthority: (input.mode === "resume" ? existing?.effective : effective)?.executionAuthority,
|
|
231
|
+
instanceCode: role.instanceCode
|
|
232
|
+
})
|
|
229
233
|
});
|
|
230
234
|
}
|
|
231
235
|
planGlobalRole(input) {
|
|
@@ -258,7 +262,10 @@ export class FileRoleLaunchPlanner {
|
|
|
258
262
|
: existing?.generatedTitle !== undefined && existing.title === existing.generatedTitle
|
|
259
263
|
? globalRoleSessionTitle(role.name, new Date(existing.createdAt), this.store.getConfig().timeZone, role.instanceCode)
|
|
260
264
|
?? `Yui ${role.name}`
|
|
261
|
-
: undefined, input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, effective, { purpose: "execution"
|
|
265
|
+
: undefined, input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, effective, { purpose: "execution",
|
|
266
|
+
nativeGeneratedTitle: globalRoleSessionTitle(role.name, input.mode === "resume" && existing !== undefined ? new Date(existing.createdAt) : this.#now(), this.store.getConfig().timeZone, role.instanceCode)
|
|
267
|
+
?? (input.mode === "resume" ? `Yui ${role.name}` : undefined)
|
|
268
|
+
});
|
|
262
269
|
}
|
|
263
270
|
#compile(role, input, owner, sessionTitle, knownNativeSessionId, effective, sessionPolicy) {
|
|
264
271
|
const launchRole = effectiveRoleForLaunch(role, effective);
|
|
@@ -570,6 +577,14 @@ export class FileRoleLaunchPlanner {
|
|
|
570
577
|
YUI_DRIVER_ID: driver.id,
|
|
571
578
|
YUI_WORKSPACE: effectiveWorkspace,
|
|
572
579
|
YUI_SESSION_MANIFEST: sessionContext.sessionManifestPath,
|
|
580
|
+
// A proposal only. The independent metadata lane must prove native
|
|
581
|
+
// ownership; planning never reads/waits on title effects.
|
|
582
|
+
...(configured.adapterId !== "codex" || sessionPolicy.nativeGeneratedTitle === undefined ? {} : {
|
|
583
|
+
YUI_NATIVE_TITLE_PROPOSAL: sessionPolicy.nativeGeneratedTitle
|
|
584
|
+
}),
|
|
585
|
+
...(configured.adapterId !== "codex" || input.mode !== "resume" || existingSession?.title === undefined ? {} : {
|
|
586
|
+
YUI_NATIVE_TITLE_LOCAL: existingSession.title
|
|
587
|
+
}),
|
|
573
588
|
...(configured.adapterId === "codex"
|
|
574
589
|
? { YUI_AGENT_BASE_ARGS: JSON.stringify(configured.baseArgs) }
|
|
575
590
|
: {}),
|
|
@@ -79,7 +79,7 @@ export class GitIntegrationService {
|
|
|
79
79
|
#targetCheckout(attempt) {
|
|
80
80
|
return taskIntegrationTargetCheckout(this.store, this.environment, attempt.taskId, attempt.projectId, attempt.targetRef);
|
|
81
81
|
}
|
|
82
|
-
async integrate(taskId, integrationId, signal) {
|
|
82
|
+
async integrate(taskId, integrationId, signal, intent) {
|
|
83
83
|
let initial = requireIntegration(this.store, taskId, integrationId);
|
|
84
84
|
this.#targetCheckout(initial);
|
|
85
85
|
if (!["running", "conflicted", "blocked", "validating"].includes(initial.status)) {
|
|
@@ -185,7 +185,8 @@ export class GitIntegrationService {
|
|
|
185
185
|
try {
|
|
186
186
|
current = await applyIntegrationSource({
|
|
187
187
|
attempt: current, workspace, remoteUrl: project.remoteUrl,
|
|
188
|
-
git: this.git, store: this.store, now: this.now
|
|
188
|
+
git: this.git, store: this.store, now: this.now,
|
|
189
|
+
continueOwnedOperation: intent === "continue"
|
|
189
190
|
});
|
|
190
191
|
if (current.status === "conflicted" || current.status === "blocked") {
|
|
191
192
|
return { status: current.status, attempt: current, workspace };
|
|
@@ -135,7 +135,18 @@ export async function applyIntegrationSource(input) {
|
|
|
135
135
|
return pause(unresolved, kind);
|
|
136
136
|
}
|
|
137
137
|
if (!fresh && current.status !== "conflicted") {
|
|
138
|
-
|
|
138
|
+
if (input.continueOwnedOperation !== true) {
|
|
139
|
+
throw new Error("Git operation is present but no paused conflict is recorded; inspect its execution before continuing.");
|
|
140
|
+
}
|
|
141
|
+
// Git can have stopped before the transaction recording its conflict
|
|
142
|
+
// failed. Existing progress and assertOperation above bind this exact
|
|
143
|
+
// operation; explicit intent may continue only a staged resolution.
|
|
144
|
+
// Do not manufacture a conflict record or restart the source command.
|
|
145
|
+
if (!await succeeds(path, ["diff", "--quiet"])
|
|
146
|
+
|| await succeeds(path, ["diff", "--cached", "--quiet"])
|
|
147
|
+
|| (await command(path, ["ls-files", "--others", "--exclude-standard"])).trim() !== "") {
|
|
148
|
+
throw new Error("Owned Git recovery requires a fully staged resolution with no unstaged or untracked files.");
|
|
149
|
+
}
|
|
139
150
|
}
|
|
140
151
|
try {
|
|
141
152
|
if (operation !== undefined) {
|
|
@@ -63,32 +63,27 @@ export function taskNotificationPresentation(wake, store) {
|
|
|
63
63
|
`Changes: ${labels.slice(0, 4).join("; ")}${labels.length > 4 ? "; more in wake" : ""}.`,
|
|
64
64
|
...messages.slice(0, 4).map(message => {
|
|
65
65
|
const item = taskMessagePresentation(message, (task, run) => store.getRun(task, run));
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// Keep failure/disposition facts here. Authored summaries remain in the
|
|
67
|
+
// mandatory wake/original reads, not a second copy in every delivery.
|
|
68
|
+
return `- ${message.id} [${message.author.type}${message.author.type === "role" ? `:${message.author.roleName}` : ""}]: ${item.fact}.`;
|
|
68
69
|
}),
|
|
69
70
|
...(messages.length > 4 ? [`${messages.length - 4} more messages in the fixed wake.`] : []),
|
|
70
71
|
`Refresh: yui task context ${wake.taskId} --json`,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// Match Runtime's receiving-entry -> claim -> originals order. The claim
|
|
73
|
+
// returns the exact focused read; do not suggest reading it once beforehand.
|
|
74
|
+
`Read receiving entry: yui inbox show ${wake.id} --json`,
|
|
75
|
+
`Inbox: ${wake.id}; follow Runtime claim → original → disposition → ACK.`,
|
|
74
76
|
...(wake.reasons.includes("session-replaced") ? [
|
|
75
|
-
"Session
|
|
76
|
-
|
|
77
|
-
"Preserve original claims and delivery targets. Unknown external effects require evidence, not another execution."
|
|
78
|
-
] : []),
|
|
79
|
-
"Check current Skills each turn: yui session skills --json. Apply current yui-leader; read relevant originals in full and advance authorized work."
|
|
77
|
+
"Session replaced: yui inbox list --view history --json; follow Runtime inbox recovery before settling retained claims. Do not replay unknown effects."
|
|
78
|
+
] : [])
|
|
80
79
|
].join("\n");
|
|
81
80
|
}
|
|
82
81
|
export function globalMessagePresentation(roleName, messageId, message) {
|
|
83
|
-
const summary = authoredNotificationSummary(message?.body);
|
|
84
82
|
return [
|
|
85
83
|
`Yui Global Message: role=${roleName} message=${messageId}.`,
|
|
86
84
|
`Changes: ${message?.author.type ?? "Recorded"} input received.`,
|
|
87
|
-
...(summary === undefined ? [] : [`Author summary (message-body): ${JSON.stringify(summary)}`]),
|
|
88
85
|
"Refresh Session Context using the current Manifest's loadCommand.",
|
|
89
|
-
`
|
|
90
|
-
`
|
|
91
|
-
"Routing to a Task or asking a necessary user question can settle this input without completing the Task. Missing ACK is not permission to repeat effects.",
|
|
92
|
-
`The Global loadCommand checks current Skill versions each turn. Apply current ${roleName === "operator" ? "yui-operator" : "yui-runtime"} within existing authority.`
|
|
86
|
+
`Original: reuse the complete matching currentOriginals record from Context; if absent, yui inbox show ${messageId} --json.`,
|
|
87
|
+
`Inbox: ${messageId}; follow Runtime claim → original → disposition → ACK.`
|
|
93
88
|
].join("\n");
|
|
94
89
|
}
|
|
@@ -10,7 +10,7 @@ export function createOperatorBatchPresentation(batchId, items) {
|
|
|
10
10
|
receiptId: `operator-batch:${batchId}`,
|
|
11
11
|
text: [
|
|
12
12
|
"[Yui updates]",
|
|
13
|
-
"
|
|
13
|
+
"Apply current yui-operator and refresh Global Context; inspect these originals and present material changes or decisions.",
|
|
14
14
|
...items.flatMap(renderItem)
|
|
15
15
|
].join("\n")
|
|
16
16
|
};
|
|
@@ -12,6 +12,7 @@ import { sourceRunContextValue } from "../context/sourceRunContext.js";
|
|
|
12
12
|
import { requireManagedTaskCaller } from "../runtime/managedCaller.js";
|
|
13
13
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
14
14
|
import { isTaskRecordRetired, operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
15
|
+
import { recoveredMessageContinuation } from "../controller/taskRunRecovery.js";
|
|
15
16
|
export function resolveMessageRecipient(store, taskId, roleName, scope) {
|
|
16
17
|
if (scope.reviewRoundId !== undefined) {
|
|
17
18
|
const round = store.getReviewRound(taskId, scope.reviewRoundId);
|
|
@@ -80,7 +81,8 @@ export function messageAssignmentBlocker(store, message) {
|
|
|
80
81
|
const latest = store.listRuns(message.taskId).filter((run) => run.workItemId === owner.workItemId && run.reviewRoundId === owner.reviewRoundId
|
|
81
82
|
&& run.roleName === owner.roleName).at(-1);
|
|
82
83
|
if (latest !== undefined && latest.id !== owner.id
|
|
83
|
-
&& latest.inputs[0]?.input.source.channel !== "message-continuation"
|
|
84
|
+
&& latest.inputs[0]?.input.source.channel !== "message-continuation"
|
|
85
|
+
&& !recoveredMessageContinuation(store, message, latest))
|
|
84
86
|
return "assignment-changed";
|
|
85
87
|
if (target.reviewRoundId !== undefined) {
|
|
86
88
|
const validation = validateExactRunReviewRound(store, latest ?? owner, { allowTerminal: true });
|
|
@@ -35,10 +35,17 @@ export function readDigest(value) {
|
|
|
35
35
|
}
|
|
36
36
|
/** A save receipt is not another read of the submitted body. Keep the exact
|
|
37
37
|
* delivery/control facts and identity; body/history are read on demand.
|
|
38
|
+
* bodySha256 hashes the saved string's UTF-8 bytes, not its JSON encoding or
|
|
39
|
+
* record metadata. It permits transport comparison, not semantic read credit.
|
|
38
40
|
*/
|
|
39
41
|
export function messageReceipt(message) {
|
|
40
42
|
const { body: _body, ...facts } = message;
|
|
41
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
...facts,
|
|
45
|
+
digest: readDigest(message),
|
|
46
|
+
bodyBytes: Buffer.byteLength(message.body, "utf8"),
|
|
47
|
+
bodySha256: createHash("sha256").update(message.body, "utf8").digest("hex")
|
|
48
|
+
};
|
|
42
49
|
}
|
|
43
50
|
export function encodeReadCursor(cursor) {
|
|
44
51
|
return Buffer.from(JSON.stringify(cursor)).toString("base64url");
|
|
@@ -117,15 +124,37 @@ export function boundedDocument(value, source, cursor) {
|
|
|
117
124
|
const offset = cursor === undefined ? 0 : decodeReadCursor(cursor, source, digest);
|
|
118
125
|
if (offset >= text.length && offset !== 0)
|
|
119
126
|
throw usageError("Read cursor exceeds this document.");
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
end--;
|
|
124
|
-
return { contentPage: {
|
|
127
|
+
const boundary = (end) => end < text.length && /[\uD800-\uDBFF]/u.test(text[end - 1])
|
|
128
|
+
? end - 1 : end;
|
|
129
|
+
const page = (end) => ({ contentPage: {
|
|
125
130
|
source, digest, encoding: "json", offset, totalCharacters: text.length,
|
|
126
131
|
totalBytes, text: text.slice(offset, end), complete: end === text.length,
|
|
127
132
|
nextCursor: end < text.length ? encodeReadCursor({ source, digest, offset: end }) : null
|
|
128
|
-
} };
|
|
133
|
+
} });
|
|
134
|
+
// A code unit costs at least one encoded byte. Bound each trial independently
|
|
135
|
+
// of document size, and budget the entire response including escaped identity.
|
|
136
|
+
const upper = boundary(Math.min(offset + READ_PAGE_BYTES, text.length));
|
|
137
|
+
const largest = page(upper);
|
|
138
|
+
if (fitsReadBudget(largest))
|
|
139
|
+
return largest;
|
|
140
|
+
// The terminal page drops its cursor: test it above, separately from the
|
|
141
|
+
// monotone non-terminal search. Raw indices advance even at surrogate pairs.
|
|
142
|
+
let low = offset + 1;
|
|
143
|
+
let high = upper - 1;
|
|
144
|
+
let end = offset;
|
|
145
|
+
while (low <= high) {
|
|
146
|
+
const middle = Math.floor((low + high) / 2);
|
|
147
|
+
const candidate = boundary(middle);
|
|
148
|
+
if (fitsReadBudget(page(candidate))) {
|
|
149
|
+
end = candidate;
|
|
150
|
+
low = middle + 1;
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
high = middle - 1;
|
|
154
|
+
}
|
|
155
|
+
if (end === offset)
|
|
156
|
+
throw usageError("Read identity and text exceed the response budget.");
|
|
157
|
+
return page(end);
|
|
129
158
|
}
|
|
130
159
|
/** Shared parser for list/detail command tails; no options reach mutations. */
|
|
131
160
|
export function readOptions(args, flags = ["--cursor", "--limit"]) {
|
|
@@ -122,7 +122,9 @@ function permission(binding) {
|
|
|
122
122
|
return "bypass";
|
|
123
123
|
return [
|
|
124
124
|
permission.sandbox === undefined ? undefined : `sandbox=${permission.sandbox}`,
|
|
125
|
-
permission.approval === undefined ? undefined : `approval=${permission.approval}
|
|
125
|
+
permission.approval === undefined ? undefined : `approval=${permission.approval}`,
|
|
126
|
+
permission.excludeTmpdirEnvVar === undefined ? undefined : `excludeTmpdirEnvVar=${permission.excludeTmpdirEnvVar}`,
|
|
127
|
+
permission.excludeSlashTmp === undefined ? undefined : `excludeSlashTmp=${permission.excludeSlashTmp}`
|
|
126
128
|
].filter((value) => value !== undefined).join("; ");
|
|
127
129
|
}
|
|
128
130
|
// ACP's strategy decides the Session mode Yui requests over the protocol. It
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1
2
|
import { builtinAgentEndpointImplementation, requireBuiltinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
|
|
2
3
|
import { builtinAgentDriverRegistry } from "./builtinAgentDrivers.js";
|
|
3
4
|
import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
|
|
@@ -80,6 +81,12 @@ function freezeConfiguration(value) {
|
|
|
80
81
|
class BuiltinAgentEndpoint {
|
|
81
82
|
driver;
|
|
82
83
|
configuration;
|
|
84
|
+
get contextEpoch() { return this.driver.contextEpoch; }
|
|
85
|
+
async checkQuiescent() {
|
|
86
|
+
if (!this.driver.checkQuiescent)
|
|
87
|
+
throw new Error("This native Endpoint cannot prove background quiescence.");
|
|
88
|
+
return this.driver.checkQuiescent();
|
|
89
|
+
}
|
|
83
90
|
get nativeAccess() { return this.driver.nativeAccess; }
|
|
84
91
|
get appliedGeneratedTitle() { return this.driver.appliedGeneratedTitle; }
|
|
85
92
|
get ownedProcessId() { return this.driver.ownedProcessId; }
|
|
@@ -147,6 +154,7 @@ class BuiltinAgentEndpoint {
|
|
|
147
154
|
const previous = this.#attempts.get(input.attemptId);
|
|
148
155
|
if (previous !== undefined) {
|
|
149
156
|
if (previous.input.inputRef !== input.inputRef || previous.input.boundedText !== input.boundedText
|
|
157
|
+
|| !isDeepStrictEqual(previous.input.additionalContext, input.additionalContext)
|
|
150
158
|
|| previous.operation !== operation) {
|
|
151
159
|
throw new Error("Endpoint attempt identity cannot be reused for different input or operation.");
|
|
152
160
|
}
|
|
@@ -160,7 +168,9 @@ class BuiltinAgentEndpoint {
|
|
|
160
168
|
return { status: "not-submitted", error: new ProviderTurnRejectedError("Endpoint is detached.", input.attemptId) };
|
|
161
169
|
}
|
|
162
170
|
const attempt = {
|
|
163
|
-
input: Object.freeze({ ...input }
|
|
171
|
+
input: Object.freeze({ ...input, ...(input.additionalContext === undefined ? {} : {
|
|
172
|
+
additionalContext: structuredClone(input.additionalContext)
|
|
173
|
+
}) }),
|
|
164
174
|
operation,
|
|
165
175
|
disposition: { status: "pending", reason: "submitting" }
|
|
166
176
|
};
|