@zq-silk/yui 0.14.2 → 0.15.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/ARCHITECTURE.md +27 -12
- package/README.md +52 -52
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/cli/updateCommand.js +17 -9
- package/dist/cli/updateOrchestrator.js +81 -15
- package/dist/cli/updatePorts.js +72 -10
- package/dist/cli/upgradeCommand.js +104 -19
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/core/controllerServer.js +5 -5
- package/dist/doctor/doctor.js +37 -14
- package/dist/observability/runtimeIdentity.js +48 -50
- package/dist/release/runtimeRelease.js +9 -1
- package/dist/runtime/exactControlPlane.js +20 -29
- package/dist/storage/currentTaskStore.js +6 -4
- package/dist/storage/sqliteSchema.js +134 -59
- package/dist/storage/sqliteStore.js +7 -5
- package/dist/storage/storageSchema.js +92 -223
- package/dist/storage/storageVersions.js +12 -16
- package/dist/storage/upgrade/upgradeOrchestrator.js +224 -62
- package/dist/version.js +3 -3
- package/docs/task-local-identity.md +9 -9
- package/i18n/README.zh-CN.md +26 -17
- package/package.json +1 -1
- package/dist/storage/upgrade/recordVersions.js +0 -82
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { basename, dirname, join } from "node:path";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
3
4
|
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../runtime/runtimeObservation.js";
|
|
4
5
|
import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
|
|
5
6
|
import { validateAgentProfile } from "../../profile/agentProfile.js";
|
|
@@ -8,56 +9,168 @@ import { validateReviewRound } from "../../review/reviewRound.js";
|
|
|
8
9
|
import { validateTurn } from "../../turn/turn.js";
|
|
9
10
|
import { validateWorkItem } from "../../workItem/workItem.js";
|
|
10
11
|
import { SqliteTaskStore } from "../sqliteStore.js";
|
|
11
|
-
import {
|
|
12
|
-
|
|
12
|
+
import { migrateSqliteSchema, storageMigrationPlan } from "../sqliteSchema.js";
|
|
13
|
+
import { CURRENT_DATABASE_FILENAME, inspectStorageSchema } from "../storageSchema.js";
|
|
14
|
+
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../storageVersions.js";
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Upgrade one valid Home through the complete linear migration chain.
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* Supported earlier versions are readable only here. Ordinary stores still
|
|
19
|
+
* admit exactly {@link CURRENT_STORAGE_VERSION}; no old-shape normalizer or
|
|
20
|
+
* dual read path enters runtime code. Execute mode is an offline primitive:
|
|
21
|
+
* its caller must own the maintenance fence and keep the Controller quiesced
|
|
22
|
+
* for the whole backup, migration, and validation interval.
|
|
19
23
|
*/
|
|
20
24
|
export async function runStorageUpgrade(options) {
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
return blocked({ ...
|
|
25
|
+
const state = inspectStorageSchema(options.home);
|
|
26
|
+
if (state.status === "uninitialized") {
|
|
27
|
+
return blocked({ ...corruptedClassification("Storage is not initialized."), uninitialized: true }, "uninitialized", "Yui storage is not initialized for this Home.", "Run `yui setup` with a new Home.");
|
|
24
28
|
}
|
|
25
|
-
if (
|
|
26
|
-
return blocked(corruptedClassification(
|
|
29
|
+
if (state.status === "invalid") {
|
|
30
|
+
return blocked(corruptedClassification(state.detail), "corruption", `Storage is invalid: ${state.detail}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
27
31
|
}
|
|
28
|
-
if (
|
|
29
|
-
|
|
32
|
+
if (state.status === "unsupported") {
|
|
33
|
+
const classification = unsupportedClassification(state);
|
|
34
|
+
return blocked(classification, "unsupported", classification.classification.verdict === "NEEDS_NEW_VERSION"
|
|
35
|
+
? classification.classification.blocker.message
|
|
36
|
+
: "Storage is unsupported.", classification.classification.verdict === "NEEDS_NEW_VERSION"
|
|
37
|
+
? classification.classification.blocker.action
|
|
38
|
+
: "Use a compatible Yui release.");
|
|
30
39
|
}
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
if (state.status === "current") {
|
|
41
|
+
try {
|
|
42
|
+
validateCurrentStore(options.home);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return blocked(corruptedClassification(messageOf(error)), "corruption", `Current storage validation failed: ${messageOf(error)}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
46
|
+
}
|
|
47
|
+
const classification = currentClassification();
|
|
48
|
+
if (options.mode === "update-preflight") {
|
|
49
|
+
return {
|
|
50
|
+
outcome: "update-preflight",
|
|
51
|
+
status: "already-current",
|
|
52
|
+
stepCount: 0,
|
|
53
|
+
steps: [],
|
|
54
|
+
classification
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
outcome: "already-current",
|
|
59
|
+
classification,
|
|
60
|
+
report: {
|
|
61
|
+
outcome: "already-current",
|
|
62
|
+
mode: options.mode,
|
|
63
|
+
sourceVersion: CURRENT_STORAGE_VERSION,
|
|
64
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
65
|
+
steps: []
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const plan = storageMigrationPlan(state.currentVersion);
|
|
70
|
+
if (plan === null) {
|
|
71
|
+
return blocked(corruptedClassification(`No complete migration path exists from ${state.currentVersion} `
|
|
72
|
+
+ `to ${CURRENT_STORAGE_VERSION}.`), "corruption", "The storage migration registry is incomplete.", "Install a Yui release that carries the complete migration chain.");
|
|
73
|
+
}
|
|
74
|
+
const classification = migratableClassification(state.currentVersion);
|
|
75
|
+
if (options.mode === "update-preflight") {
|
|
76
|
+
return {
|
|
77
|
+
outcome: "update-preflight",
|
|
78
|
+
status: "migration-ready",
|
|
79
|
+
stepCount: plan.length,
|
|
80
|
+
steps: plan,
|
|
81
|
+
classification
|
|
82
|
+
};
|
|
36
83
|
}
|
|
84
|
+
if (options.mode === "dry-run") {
|
|
85
|
+
return {
|
|
86
|
+
outcome: "upgrade-plan",
|
|
87
|
+
classification,
|
|
88
|
+
report: {
|
|
89
|
+
outcome: "upgrade-plan",
|
|
90
|
+
mode: "dry-run",
|
|
91
|
+
sourceVersion: state.currentVersion,
|
|
92
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
93
|
+
steps: plan
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
let backupPath;
|
|
37
98
|
try {
|
|
99
|
+
backupPath = await createDatabaseBackup(options.home, state.currentVersion, options.now ?? new Date());
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
outcome: "failed",
|
|
104
|
+
stage: "backup",
|
|
105
|
+
message: `Storage backup failed: ${messageOf(error)}`,
|
|
106
|
+
action: "Storage was not modified. Resolve the backup path, permissions, or free-space problem "
|
|
107
|
+
+ "and rerun `yui upgrade`.",
|
|
108
|
+
classification,
|
|
109
|
+
sceneUnchanged: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
let migrationCommitted = false;
|
|
113
|
+
try {
|
|
114
|
+
const database = new Database(join(options.home, CURRENT_DATABASE_FILENAME));
|
|
115
|
+
try {
|
|
116
|
+
database.pragma("journal_mode = WAL");
|
|
117
|
+
database.pragma("synchronous = FULL");
|
|
118
|
+
database.pragma("foreign_keys = ON");
|
|
119
|
+
database.pragma("busy_timeout = 5000");
|
|
120
|
+
migrateSqliteSchema(database, { mode: "apply" });
|
|
121
|
+
migrationCommitted = true;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
database.close();
|
|
125
|
+
}
|
|
38
126
|
validateCurrentStore(options.home);
|
|
127
|
+
rmSync(join(options.home, "schema.json"), { force: true });
|
|
39
128
|
}
|
|
40
129
|
catch (error) {
|
|
41
|
-
|
|
130
|
+
const restoration = migrationCommitted
|
|
131
|
+
? tryRestoreDatabaseBackup(options.home, backupPath)
|
|
132
|
+
: { restored: true };
|
|
133
|
+
return {
|
|
134
|
+
outcome: "failed",
|
|
135
|
+
stage: "migration",
|
|
136
|
+
message: `Storage migration failed: ${messageOf(error)}`,
|
|
137
|
+
action: restoration.restored
|
|
138
|
+
? "The original database was restored from the timestamped backup. "
|
|
139
|
+
+ "Resolve the reported problem and rerun `yui upgrade`."
|
|
140
|
+
: "Automatic restore also failed. Keep the Home quiesced and restore "
|
|
141
|
+
+ `${backupPath} manually before retrying. Restore error: ${restoration.error}`,
|
|
142
|
+
backupPath,
|
|
143
|
+
classification,
|
|
144
|
+
sceneUnchanged: restoration.restored
|
|
145
|
+
};
|
|
42
146
|
}
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
147
|
+
const finalState = inspectStorageSchema(options.home);
|
|
148
|
+
if (finalState.status !== "current") {
|
|
149
|
+
const restoration = tryRestoreDatabaseBackup(options.home, backupPath);
|
|
45
150
|
return {
|
|
46
|
-
outcome: "
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
151
|
+
outcome: "failed",
|
|
152
|
+
stage: "migration",
|
|
153
|
+
message: `Storage migration did not reach version ${CURRENT_STORAGE_VERSION}.`,
|
|
154
|
+
action: restoration.restored
|
|
155
|
+
? "The original database was restored from the timestamped backup. "
|
|
156
|
+
+ "Inspect the migration registry before retrying."
|
|
157
|
+
: "Automatic restore also failed. Keep the Home quiesced and restore "
|
|
158
|
+
+ `${backupPath} manually before retrying. Restore error: ${restoration.error}`,
|
|
159
|
+
backupPath,
|
|
160
|
+
classification,
|
|
161
|
+
sceneUnchanged: restoration.restored
|
|
51
162
|
};
|
|
52
163
|
}
|
|
53
164
|
return {
|
|
54
|
-
outcome: "
|
|
55
|
-
classification,
|
|
165
|
+
outcome: "upgraded",
|
|
166
|
+
classification: currentClassification(),
|
|
56
167
|
report: {
|
|
57
|
-
outcome: "
|
|
58
|
-
mode:
|
|
59
|
-
|
|
60
|
-
|
|
168
|
+
outcome: "upgraded",
|
|
169
|
+
mode: "execute",
|
|
170
|
+
sourceVersion: state.currentVersion,
|
|
171
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
172
|
+
steps: plan,
|
|
173
|
+
backupPath
|
|
61
174
|
}
|
|
62
175
|
};
|
|
63
176
|
}
|
|
@@ -96,59 +209,108 @@ function validateCurrentStore(home) {
|
|
|
96
209
|
}
|
|
97
210
|
}
|
|
98
211
|
}
|
|
212
|
+
const quickCheck = store.databaseHandle().pragma("quick_check", { simple: true });
|
|
213
|
+
if (quickCheck !== "ok") {
|
|
214
|
+
throw new Error(`SQLite quick_check failed: ${String(quickCheck)}.`);
|
|
215
|
+
}
|
|
99
216
|
}
|
|
100
217
|
finally {
|
|
101
218
|
store.close();
|
|
102
219
|
}
|
|
103
220
|
}
|
|
104
|
-
function
|
|
221
|
+
async function createDatabaseBackup(home, sourceVersion, now) {
|
|
222
|
+
const source = join(home, CURRENT_DATABASE_FILENAME);
|
|
223
|
+
if (!existsSync(source))
|
|
224
|
+
throw new Error("The authoritative yui.db is missing.");
|
|
225
|
+
const backupDirectory = join(dirname(home), `${basename(home)}-backups`);
|
|
226
|
+
mkdirSync(backupDirectory, { recursive: true, mode: 0o700 });
|
|
227
|
+
const timestamp = now.toISOString().replaceAll(":", "-");
|
|
228
|
+
const backupPath = join(backupDirectory, `pre-storage-v${sourceVersion}-${timestamp}.db`);
|
|
229
|
+
const database = new Database(source, { readonly: true, fileMustExist: true });
|
|
230
|
+
try {
|
|
231
|
+
await database.backup(backupPath);
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
database.close();
|
|
235
|
+
}
|
|
236
|
+
return backupPath;
|
|
237
|
+
}
|
|
238
|
+
function restoreDatabaseBackup(home, backupPath) {
|
|
239
|
+
const databasePath = join(home, CURRENT_DATABASE_FILENAME);
|
|
240
|
+
rmSync(`${databasePath}-wal`, { force: true });
|
|
241
|
+
rmSync(`${databasePath}-shm`, { force: true });
|
|
242
|
+
copyFileSync(backupPath, databasePath);
|
|
243
|
+
}
|
|
244
|
+
function tryRestoreDatabaseBackup(home, backupPath) {
|
|
245
|
+
try {
|
|
246
|
+
restoreDatabaseBackup(home, backupPath);
|
|
247
|
+
return { restored: true };
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return { restored: false, error: messageOf(error) };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function currentClassification() {
|
|
105
254
|
return {
|
|
106
255
|
classification: { verdict: "USABLE", status: "current" },
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
latestAggregateVersion: latest.aggregate
|
|
256
|
+
storageVersion: CURRENT_STORAGE_VERSION,
|
|
257
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
258
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
111
259
|
};
|
|
112
260
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
261
|
+
function migratableClassification(storageVersion) {
|
|
262
|
+
return {
|
|
263
|
+
classification: { verdict: "MIGRATABLE", status: "migration-ready" },
|
|
264
|
+
storageVersion,
|
|
265
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
266
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function unsupportedClassification(state) {
|
|
270
|
+
const future = state.direction === "newer";
|
|
271
|
+
const message = future
|
|
272
|
+
? `Storage version ${state.currentVersion} is newer than this CLI supports `
|
|
273
|
+
+ `(${CURRENT_STORAGE_VERSION}).`
|
|
274
|
+
: `Storage version ${state.currentVersion} is older than the minimum supported `
|
|
275
|
+
+ `migration version ${MIN_SUPPORTED_STORAGE_VERSION}.`;
|
|
118
276
|
const action = future
|
|
119
|
-
? "Use a newer Yui release
|
|
120
|
-
: "
|
|
277
|
+
? "Use a newer Yui release."
|
|
278
|
+
: "Preserve this Home for use with its matching historical Yui release, "
|
|
279
|
+
+ "or initialize a new Home with Yui 0.15.0 or later.";
|
|
121
280
|
return {
|
|
122
281
|
classification: {
|
|
123
282
|
verdict: "NEEDS_NEW_VERSION",
|
|
124
283
|
status: "unsupported",
|
|
125
284
|
blocker: {
|
|
126
|
-
reason: future ? "future-version" : "
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
message
|
|
131
|
-
? "The Home is newer than this Yui release."
|
|
132
|
-
: "This release provides no migration path for the older Home.",
|
|
285
|
+
reason: future ? "future-version" : "below-minimum",
|
|
286
|
+
found: state.currentVersion,
|
|
287
|
+
current: CURRENT_STORAGE_VERSION,
|
|
288
|
+
minimum: MIN_SUPPORTED_STORAGE_VERSION,
|
|
289
|
+
message,
|
|
133
290
|
action
|
|
134
291
|
}
|
|
135
292
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
latestAggregateVersion: latest.aggregate,
|
|
140
|
-
incompatibleComponent: axis
|
|
293
|
+
storageVersion: state.currentVersion,
|
|
294
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
295
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
141
296
|
};
|
|
142
297
|
}
|
|
143
|
-
function corruptedClassification(
|
|
298
|
+
function corruptedClassification(detail) {
|
|
144
299
|
return {
|
|
145
300
|
classification: { verdict: "CORRUPTED", status: "unsupported", detail },
|
|
146
|
-
|
|
147
|
-
|
|
301
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
302
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
148
303
|
};
|
|
149
304
|
}
|
|
150
305
|
function blocked(classification, stage, message, action) {
|
|
151
|
-
return {
|
|
306
|
+
return {
|
|
307
|
+
outcome: "blocked",
|
|
308
|
+
stage,
|
|
309
|
+
message,
|
|
310
|
+
action,
|
|
311
|
+
classification,
|
|
312
|
+
sceneUnchanged: true
|
|
313
|
+
};
|
|
152
314
|
}
|
|
153
315
|
function messageOf(error) {
|
|
154
316
|
return error instanceof Error ? error.message : String(error);
|
package/dist/version.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { FILE_TASK_CONTROLLER_PROTOCOL_VERSION } from "./core/protocol.js";
|
|
3
|
-
import {
|
|
3
|
+
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storage/storageVersions.js";
|
|
4
4
|
export const YUI_VERSION = readPackageVersion();
|
|
5
5
|
export function yuiVersionIdentity() {
|
|
6
6
|
return {
|
|
7
7
|
version: YUI_VERSION,
|
|
8
8
|
controllerProtocolVersion: FILE_TASK_CONTROLLER_PROTOCOL_VERSION,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
storageVersion: CURRENT_STORAGE_VERSION,
|
|
10
|
+
minimumStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
function readPackageVersion() {
|
|
@@ -51,12 +51,12 @@ There is no compatibility lookup, cross-Task guess, or bare-ID fallback.
|
|
|
51
51
|
|
|
52
52
|
## Current-schema boundary
|
|
53
53
|
|
|
54
|
-
Runtime opens only
|
|
55
|
-
dual-read
|
|
56
|
-
|
|
57
|
-
and
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
Runtime opens only the current Home storage version and current record shapes.
|
|
55
|
+
It does not dual-read or infer historical records during ordinary work.
|
|
56
|
+
Historical decoding and rewriting are confined to the explicit `yui upgrade`
|
|
57
|
+
boundary and the migration phase of `yui update`; every valid Home at or above
|
|
58
|
+
the CLI's minimum supported storage version can advance directly to current.
|
|
59
|
+
|
|
60
|
+
This boundary keeps Task-local references, Role desired configuration, and
|
|
61
|
+
immutable Turn/RoleSession effective snapshots under one unambiguous runtime
|
|
62
|
+
contract while the append-only migration chain preserves supported history.
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -101,7 +101,11 @@ export YUI_HOME=/absolute/path/to/yui-home
|
|
|
101
101
|
yui setup
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
home
|
|
104
|
+
home 中包含权威 SQLite 数据库 `yui.db`、Project Catalog、项目知识和
|
|
105
|
+
Controller 发现文件。稳定 Project checkout 与受管理 worktree 位于 home
|
|
106
|
+
外部的 workspace。旧 `schema.json` 与 `state.json` 只作为历史证据存在,
|
|
107
|
+
不再是版本权威。运行时只接受当前存储契约;支持区间内的早期存储版本只允许
|
|
108
|
+
通过显式升级入口。
|
|
105
109
|
|
|
106
110
|
所有 Task-owned 记录族都在各自 Task 内分配单调递增的本地 ID。因此,不同
|
|
107
111
|
Task 可以同时拥有 `work-item-1`、`turn-1` 或 `input-1`。受管 Task
|
|
@@ -111,8 +115,12 @@ Task 的命令(例如 `task work create`、`task integration start`)仍使
|
|
|
111
115
|
Task 内的本地子记录 ID。Candidate 只在所属 WorkItem 内递增,并同时保存
|
|
112
116
|
Task 与 WorkItem provenance。
|
|
113
117
|
|
|
114
|
-
Yui
|
|
115
|
-
|
|
118
|
+
Yui Home 只有一个存储版本,权威值是 SQLite 中连续且校验和有效的迁移
|
|
119
|
+
ledger 头。CLI 同时公布当前存储版本和最小支持迁移版本。普通运行时代码只读取
|
|
120
|
+
最新结构,不提供双读;`yui upgrade --dry-run` 只显示迁移计划,
|
|
121
|
+
`yui upgrade` 会停住正在运行的 Controller、创建一致性备份、在一个事务中执行
|
|
122
|
+
全部缺失迁移并校验最新结构。处于支持区间内的任意历史 Home 都可以直接跨多个
|
|
123
|
+
版本升级,无需逐个安装中间版本。当前引用契约见
|
|
116
124
|
[Task 本地 ID](../docs/task-local-identity.md)。
|
|
117
125
|
|
|
118
126
|
## 快速开始
|
|
@@ -627,13 +635,13 @@ yui task role release <task-id> <role>
|
|
|
627
635
|
|
|
628
636
|
Codex Role thread 可在 Desktop 中直接查看和操作;Desktop 已有 active Turn 时,Yui 只保留待投递工作并等待,不会失败或重复投递。`view`、`takeover`、`release` 继续作为 Claude 等独立进程 Provider 的人工控制入口。Yui 不写入全局 Hook/config,也不启动、重启或停止共享 daemon;Codex CLI/daemon 故障由 Task 生命周期之外修复。Global Operator 与 global Role 继续使用原生交互式 CLI,不属于受管理 Task Provider 协议;Yui 在内部将 Codex 的 Global TUI 连接到同一个默认 App Server,用户不能通过 Agent 或 Role 参数覆盖该连接,Session Manifest 自带不依赖启动进程环境的 Global Context 命令,因此同一 thread 可直接切换到 Desktop 继续对话。
|
|
629
637
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
638
|
+
`yui update` 会用目标版本先做只读预检,在停住精确的旧 Controller 后自动执行
|
|
639
|
+
所需的离线迁移,再校验并启动新 Controller。若升级前希望结束所有 Agent
|
|
640
|
+
活动,可先执行 `yui session stop --all`;这不是存储版本链的一部分。
|
|
641
|
+
Yui 0.15.0 建立 storage version 1 和迁移下限。更早版本(包括 0.14.2)
|
|
642
|
+
创建的 Home 不在这条兼容链上:应保留给匹配的历史 Yui 版本查看,或者初始化
|
|
643
|
+
新的 Home。从 0.15.0 开始,后续版本必须保留完整迁移链,因此可以由
|
|
644
|
+
`yui update` 直接跨版本升级。
|
|
637
645
|
|
|
638
646
|
tmux 会在 pane 创建时固定其历史容量。配置该限制之前创建的 Role 会保留原容量;Yui 会在 Terminal attach 和 Web 中提示用户退出并重新进入一次,从而创建具有 100,000 行历史的新 pane。
|
|
639
647
|
|
|
@@ -675,10 +683,10 @@ yui controller restart
|
|
|
675
683
|
|
|
676
684
|
`controller restart` 会用当前安装的 Yui 版本替换 Controller 进程及其调度循环、socket 服务,不会停止或重启已受管的 tmux/Agent 会话;普通 Session 命令按协议与存储身份兼容,不要求 Controller 与 CLI 包版本完全相同。
|
|
677
685
|
|
|
678
|
-
成功的 `setup
|
|
679
|
-
Controller
|
|
680
|
-
`upgrade --dry-run` 不会启动 Controller。`update`
|
|
681
|
-
|
|
686
|
+
成功的 `setup` 和 `update` 会确保当前 Home 有一个运行中的 Controller。
|
|
687
|
+
`upgrade` 只会在迁移前存在 Controller 时恢复它;只读命令和
|
|
688
|
+
`upgrade --dry-run` 不会启动 Controller。`update` 只有在迁移和新二进制健康
|
|
689
|
+
检查都通过后,才会替换或启动 Controller。
|
|
682
690
|
|
|
683
691
|
恢复 reconciliation 默认每 120 秒执行一次。普通持久状态变化只会将 Task、Role 或 Operator key 放入队列并立即返回;固定 100ms 窗口内到达的 key 会合并触发一次不重叠的定向处理。Operator 呈现使用独立 lane,不会被 Task 的 Git/worktree 操作阻塞;周期 Git/worktree 处理只覆盖仍有持久 Task mailbox 工作的 Task,活动 Role 的存活检查合并为一次 tmux inventory。来自 Provider 原生事件或受支持 Hook 的结构化 Agent Driver observation,会经过精确 fence 后进入持久 runtime inbox。终态 Turn observation 会原子记录精确的 Turn 结果。持久 WorkMailbox 会冻结当前 processing 批次,期间的新事件合并到下一 pending 批次;失败会释放当前批次供恢复。推荐输入与 pending Turn 共用最近 deadline 选择器,不依赖恢复扫描间隔;显式 `task reconcile` 仍会立即请求恢复扫描。保留的闭环为:
|
|
684
692
|
|
|
@@ -724,6 +732,7 @@ Web 端可以通过与 Terminal 相同的持久化 CLI 路径回答 open InputRe
|
|
|
724
732
|
|
|
725
733
|
```sh
|
|
726
734
|
yui update
|
|
735
|
+
yui upgrade [--dry-run]
|
|
727
736
|
yui config agent add|list|show|capabilities|update|remove
|
|
728
737
|
yui config role add|list|show|update|remove|bind|unbind
|
|
729
738
|
yui config profile add|list|show|update|remove|reset
|
|
@@ -749,9 +758,9 @@ npm test
|
|
|
749
758
|
npm run lint
|
|
750
759
|
```
|
|
751
760
|
|
|
752
|
-
`npm test` 只保留秒级核心 smoke:CLI 启动、正常 SQLite Task
|
|
753
|
-
Agent Driver。针对当前修改编写的 TDD
|
|
754
|
-
|
|
761
|
+
`npm test` 只保留秒级核心 smoke:CLI 启动、正常 SQLite Task、存储基线、
|
|
762
|
+
目标驱动更新和内置 Agent Driver。针对当前修改编写的 TDD、异常数据和故障复现
|
|
763
|
+
仅作为开发期证据,需求完成后删除,不累积为常驻回归测试。具体约束见
|
|
755
764
|
[验证策略](../docs/testing/verification-levels.md)。
|
|
756
765
|
|
|
757
766
|
如需让用户终端使用当前 checkout,可逆地接管用户级 `yui` 命令:
|
package/package.json
CHANGED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/** Current record-family versions for the single SQLite storage contract. */
|
|
2
|
-
import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storageVersions.js";
|
|
3
|
-
import { CURRENT_TURN_SCHEMA_VERSION, CURRENT_AGENT_PROFILE_SCHEMA_VERSION, CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION, CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION, CURRENT_CHANGE_SET_SCHEMA_VERSION, CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION, CURRENT_DECISION_SCHEMA_VERSION, CURRENT_EVENT_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_INPUT_REQUEST_SCHEMA_VERSION, CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION, CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION, CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION, CURRENT_MESSAGE_SCHEMA_VERSION, CURRENT_MILESTONE_SCHEMA_VERSION, CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION, CURRENT_PROJECT_SCHEMA_VERSION, CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION, CURRENT_REVIEW_ROUND_SCHEMA_VERSION, CURRENT_TASK_BRIEF_SCHEMA_VERSION, CURRENT_TASK_ROLE_SCHEMA_VERSION, CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_TASK_SCHEMA_VERSION, CURRENT_WORK_ITEM_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION } from "../taskStore.js";
|
|
4
|
-
import { CURRENT_LEADER_FAILURE_SCHEMA_VERSION } from "../../scheduler/leaderFailure.js";
|
|
5
|
-
import { CURRENT_TASK_WAKE_SCHEMA_VERSION } from "../../scheduler/taskWake.js";
|
|
6
|
-
import { CURRENT_DURABLE_JOB_SCHEMA_VERSION } from "../../job/durableJob.js";
|
|
7
|
-
function descriptor(kind, version) {
|
|
8
|
-
return Object.freeze({ version, path: `sqlite:${kind}` });
|
|
9
|
-
}
|
|
10
|
-
// Lazy construction avoids the taskStore -> storageSchema -> recordVersions
|
|
11
|
-
// initialization cycle while keeping one canonical family list.
|
|
12
|
-
let currentDescriptors = null;
|
|
13
|
-
function getCurrentRecordDescriptors() {
|
|
14
|
-
if (currentDescriptors === null) {
|
|
15
|
-
const versions = {
|
|
16
|
-
config: CURRENT_CONFIG_SCHEMA_VERSION,
|
|
17
|
-
configuredAgent: CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION,
|
|
18
|
-
project: CURRENT_PROJECT_SCHEMA_VERSION,
|
|
19
|
-
agentProfile: CURRENT_AGENT_PROFILE_SCHEMA_VERSION,
|
|
20
|
-
globalRole: CURRENT_GLOBAL_ROLE_SCHEMA_VERSION,
|
|
21
|
-
globalRoleSessionSet: CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION,
|
|
22
|
-
task: CURRENT_TASK_SCHEMA_VERSION,
|
|
23
|
-
taskBrief: CURRENT_TASK_BRIEF_SCHEMA_VERSION,
|
|
24
|
-
taskRole: CURRENT_TASK_ROLE_SCHEMA_VERSION,
|
|
25
|
-
managedWorkspace: CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION,
|
|
26
|
-
taskRoleSessionSet: CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION,
|
|
27
|
-
workItem: CURRENT_WORK_ITEM_SCHEMA_VERSION,
|
|
28
|
-
contextSnapshot: CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION,
|
|
29
|
-
turn: CURRENT_TURN_SCHEMA_VERSION,
|
|
30
|
-
reviewRound: CURRENT_REVIEW_ROUND_SCHEMA_VERSION,
|
|
31
|
-
changeSet: CURRENT_CHANGE_SET_SCHEMA_VERSION,
|
|
32
|
-
integrationAttempt: CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION,
|
|
33
|
-
integrationQueue: CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION,
|
|
34
|
-
durableJob: CURRENT_DURABLE_JOB_SCHEMA_VERSION,
|
|
35
|
-
message: CURRENT_MESSAGE_SCHEMA_VERSION,
|
|
36
|
-
inputRequest: CURRENT_INPUT_REQUEST_SCHEMA_VERSION,
|
|
37
|
-
decision: CURRENT_DECISION_SCHEMA_VERSION,
|
|
38
|
-
milestone: CURRENT_MILESTONE_SCHEMA_VERSION,
|
|
39
|
-
event: CURRENT_EVENT_SCHEMA_VERSION,
|
|
40
|
-
taskWake: CURRENT_TASK_WAKE_SCHEMA_VERSION,
|
|
41
|
-
capabilityGrant: CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION,
|
|
42
|
-
releaseWorkflow: CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION,
|
|
43
|
-
publicationReference: CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION,
|
|
44
|
-
leaderFailure: CURRENT_LEADER_FAILURE_SCHEMA_VERSION,
|
|
45
|
-
workMailbox: CURRENT_WORK_MAILBOX_SCHEMA_VERSION
|
|
46
|
-
};
|
|
47
|
-
currentDescriptors = Object.freeze(Object.fromEntries(Object.entries(versions).map(([kind, version]) => [kind, descriptor(kind, version)])));
|
|
48
|
-
}
|
|
49
|
-
return currentDescriptors;
|
|
50
|
-
}
|
|
51
|
-
/** Defensive copy of the current record-family contract. */
|
|
52
|
-
export function currentRecordVersions(candidate = getCurrentRecordDescriptors()) {
|
|
53
|
-
assertRecordVersionDescriptors(candidate);
|
|
54
|
-
return { ...candidate };
|
|
55
|
-
}
|
|
56
|
-
/** Reject missing, extra, stale, or non-SQLite record descriptors. */
|
|
57
|
-
export function assertRecordVersionDescriptors(candidate = getCurrentRecordDescriptors()) {
|
|
58
|
-
const expected = getCurrentRecordDescriptors();
|
|
59
|
-
const expectedKinds = Object.keys(expected);
|
|
60
|
-
const mappedKinds = Object.keys(candidate);
|
|
61
|
-
const missing = expectedKinds.filter((kind) => !Object.hasOwn(candidate, kind));
|
|
62
|
-
const unexpected = mappedKinds.filter((kind) => !Object.hasOwn(expected, kind));
|
|
63
|
-
if (missing.length > 0 || unexpected.length > 0) {
|
|
64
|
-
throw new Error(`Record version map completeness drift: missing=${missing.join(",") || "none"}; `
|
|
65
|
-
+ `unexpected=${unexpected.join(",") || "none"}.`);
|
|
66
|
-
}
|
|
67
|
-
for (const kind of expectedKinds) {
|
|
68
|
-
const wanted = expected[kind];
|
|
69
|
-
const found = candidate[kind];
|
|
70
|
-
if (found?.version !== wanted.version || found.path !== wanted.path) {
|
|
71
|
-
throw new Error(`Record version map drift for ${kind}: expected=${wanted.version}@${wanted.path}; `
|
|
72
|
-
+ `actual=${String(found?.version)}@${String(found?.path)}.`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
export function latestStorageVersionState(recordDescriptors = getCurrentRecordDescriptors()) {
|
|
77
|
-
return {
|
|
78
|
-
layout: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
79
|
-
aggregate: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
80
|
-
record: currentRecordVersions(recordDescriptors)
|
|
81
|
-
};
|
|
82
|
-
}
|