@zq-silk/yui 2.22.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.
Files changed (47) hide show
  1. package/dist/cli/commandCatalog.js +2 -2
  2. package/dist/cli/updateCommand.js +20 -3
  3. package/dist/cli/updateOrchestrator.js +13 -0
  4. package/dist/cli/updatePorts.js +27 -0
  5. package/dist/commands/taskRoleRuntimeStatus.js +4 -0
  6. package/dist/context/currentSkills.js +32 -1
  7. package/dist/context/sessionBootstrapManifest.js +17 -4
  8. package/dist/context/sessionSkillNotice.js +33 -12
  9. package/dist/controller/clientRuntime.js +37 -18
  10. package/dist/controller/controller.js +16 -1
  11. package/dist/controller/fileSchedulerStoreAdapter.js +2 -0
  12. package/dist/controller/hostLifecycle.js +153 -0
  13. package/dist/controller/hostLifecycleChecks.js +27 -0
  14. package/dist/controller/hostUpgrade.js +344 -0
  15. package/dist/controller/runtime.js +67 -16
  16. package/dist/controller/sessionNotify.js +5 -46
  17. package/dist/execution/codexThreadNaming.js +54 -43
  18. package/dist/executor/fileRoleLaunchPlanner.js +18 -3
  19. package/dist/runtime/agentEndpoint.js +11 -1
  20. package/dist/runtime/agentEndpointIdentity.js +2 -0
  21. package/dist/runtime/agentHost.js +167 -42
  22. package/dist/runtime/agentHostImplementation.js +3 -1
  23. package/dist/runtime/codexAppServerRuntime.js +3 -0
  24. package/dist/runtime/codexInteractiveHost.js +62 -17
  25. package/dist/runtime/codexNativeAccess.js +1 -1
  26. package/dist/runtime/contextRecovery.js +15 -0
  27. package/dist/runtime/hostSessionLifecycle.js +83 -0
  28. package/dist/runtime/hostUpgradeGate.js +43 -0
  29. package/dist/runtime/lifecycleChecks.js +47 -0
  30. package/dist/runtime/lifecycleReservation.js +9 -1
  31. package/dist/runtime/nativeTitleReceipt.js +31 -0
  32. package/dist/runtime/nativeTitleSync.js +192 -0
  33. package/dist/runtime/providerRetry.js +4 -4
  34. package/dist/runtime/structuredProviderHost.js +42 -10
  35. package/dist/storage/sqliteSchema.js +20 -0
  36. package/dist/storage/storageVersions.js +1 -1
  37. package/docs/managed-turn-and-session-runtime.md +96 -1
  38. package/docs/managed-turn-and-session-runtime.zh-CN.md +70 -1
  39. package/docs/release-workflow.md +28 -0
  40. package/docs/release-workflow.zh-CN.md +20 -0
  41. package/docs/storage-baseline.md +10 -1
  42. package/docs/storage-baseline.zh-CN.md +5 -1
  43. package/docs/testing/verification-levels.md +2 -2
  44. package/docs/testing/verification-levels.zh-CN.md +2 -2
  45. package/package.json +1 -1
  46. package/skills/yui-runtime/SKILL.md +7 -2
  47. package/skills/yui-runtime/VERSION +1 -1
@@ -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 async function setCodexThreadName(request) {
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
- constructor(child) {
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) => { this.#receive(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 client = new CodexNamingClient(spawn(command, [...baseArgs, "app-server", "--stdio"], { env: environment, stdio: ["pipe", "pipe", "pipe"] }));
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.child.kill("SIGTERM");
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
- this.#pending.delete(message.id);
98
- if (message.error !== undefined) {
99
- const error = object(message.error, "Codex App Server error");
100
- pending.reject(new Error(typeof error.message === "string" ? error.message : "Codex App Server request failed."));
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
- else {
103
- pending.resolve(object(message.result, "Codex App Server result"));
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;
@@ -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
  : {}),
@@ -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
  };
@@ -35,6 +35,8 @@ const ENDPOINT_CHECKOUT_MODULES = Object.freeze([
35
35
  "acpProtocol.js",
36
36
  "acpSessionConfiguration.js",
37
37
  "agentRunConfiguration.js",
38
+ "contextRecovery.js",
39
+ "codexNativeAccess.js",
38
40
  "jsonLineChannel.js"
39
41
  ]);
40
42
  function resolveEndpointGeneration() {