@zq-silk/yui 2.19.0 → 2.20.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/dist/cli/commandCatalog.js +2 -0
- package/dist/commands/roleConfiguration.js +17 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +22 -2
- package/dist/controller/runtimeLaunchCoordinator.js +12 -1
- package/dist/executor/agentExecutor.js +7 -0
- package/dist/executor/fileRoleLaunchPlanner.js +22 -6
- package/dist/output/rolePresentation.js +1 -0
- package/dist/role/instanceCode.js +7 -0
- package/dist/role/role.js +10 -0
- package/dist/runtime/agentEndpoint.js +1 -0
- package/dist/runtime/agentHost.js +9 -1
- package/dist/runtime/launchBroker.js +3 -0
- package/dist/runtime/runtimeBinding.js +3 -0
- package/dist/runtime/sessionTitle.js +29 -9
- package/dist/runtime/structuredProviderHost.js +11 -1
- package/dist/runtime/tmuxAdapters.js +4 -0
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/storageVersions.js +1 -1
- package/docs/release-workflow.md +29 -0
- package/docs/release-workflow.zh-CN.md +22 -0
- package/docs/roles-and-configuration.md +49 -0
- package/docs/roles-and-configuration.zh-CN.md +38 -0
- package/docs/storage-baseline.md +4 -1
- package/docs/storage-baseline.zh-CN.md +1 -1
- package/docs/testing/verification-levels.md +2 -2
- package/docs/testing/verification-levels.zh-CN.md +1 -1
- package/package.json +1 -1
|
@@ -136,6 +136,7 @@ const agentChildren = [
|
|
|
136
136
|
{ name: "remove", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Remove a configured Agent.", usage: "yui config agent remove <id>" }
|
|
137
137
|
];
|
|
138
138
|
const roleProfileOptions = [
|
|
139
|
+
"--instance-code",
|
|
139
140
|
"--description", "--responsibility", "--constraint",
|
|
140
141
|
"--expected-output", "--system-prompt", "--skill"
|
|
141
142
|
];
|
|
@@ -144,6 +145,7 @@ const roleAgentOptions = [
|
|
|
144
145
|
"--allowed-tool", "--disallowed-tool", "--search"
|
|
145
146
|
];
|
|
146
147
|
const roleProfileClearOptions = [
|
|
148
|
+
"--clear-instance-code",
|
|
147
149
|
"--clear-description", "--clear-responsibilities", "--clear-constraints",
|
|
148
150
|
"--clear-expected-output", "--clear-system-prompt", "--clear-skills"
|
|
149
151
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
+
import { validateInstanceCode } from "../role/instanceCode.js";
|
|
2
3
|
import { parseEnabled } from "../role/availability.js";
|
|
3
4
|
import { defaultRoleAgentConfig, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
4
5
|
const PROFILE_OPTIONS = [
|
|
@@ -66,6 +67,8 @@ export function roleOptionSpecs(input) {
|
|
|
66
67
|
...(input.includeWorkspace === true ? [["--workspace", "value"]] : []),
|
|
67
68
|
...(input.update ? [["--yes", "flag"]] : []),
|
|
68
69
|
...(input.update ? [["--enabled", "value"]] : []),
|
|
70
|
+
["--instance-code", "value"],
|
|
71
|
+
...(input.update ? [["--clear-instance-code", "flag"]] : []),
|
|
69
72
|
...PROFILE_OPTIONS,
|
|
70
73
|
...agentValueOptions,
|
|
71
74
|
...agentRepeatableOptions,
|
|
@@ -105,6 +108,7 @@ export function parseRoleOptions(args, specs, usage) {
|
|
|
105
108
|
}
|
|
106
109
|
export function roleProfileFrom(parsed) {
|
|
107
110
|
return {
|
|
111
|
+
...roleDisplayPatch(parsed),
|
|
108
112
|
...(parsed.has("--description") ? { description: requiredText(parsed.one("--description")) } : {}),
|
|
109
113
|
responsibilities: parsed.many("--responsibility").map(requiredText),
|
|
110
114
|
constraints: parsed.many("--constraint").map(requiredText),
|
|
@@ -125,6 +129,7 @@ export function roleProfilePatch(parsed) {
|
|
|
125
129
|
["--skill", "--clear-skills"]
|
|
126
130
|
]);
|
|
127
131
|
return {
|
|
132
|
+
...roleDisplayPatch(parsed),
|
|
128
133
|
...(parsed.has("--enabled") ? { enabled: parseEnabled(parsed.one("--enabled")) } : {}),
|
|
129
134
|
...(parsed.has("--description") ? { description: requiredText(parsed.one("--description")) } : {}),
|
|
130
135
|
...(parsed.has("--clear-description") ? { description: undefined } : {}),
|
|
@@ -144,6 +149,18 @@ export function roleProfilePatch(parsed) {
|
|
|
144
149
|
...(parsed.has("--clear-skills") ? { skills: [] } : {})
|
|
145
150
|
};
|
|
146
151
|
}
|
|
152
|
+
function roleDisplayPatch(parsed) {
|
|
153
|
+
assertPairs(parsed, [["--instance-code", "--clear-instance-code"]]);
|
|
154
|
+
try {
|
|
155
|
+
return {
|
|
156
|
+
...(parsed.has("--instance-code") ? { instanceCode: validateInstanceCode(parsed.one("--instance-code")) } : {}),
|
|
157
|
+
...(parsed.has("--clear-instance-code") ? { instanceCode: undefined } : {})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw usageError(error instanceof Error ? error.message : String(error));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
147
164
|
export function hasAgentConfigOptions(parsed) {
|
|
148
165
|
return [...parsed.seen].some((option) => AGENT_CONFIG_OPTIONS.has(option));
|
|
149
166
|
}
|
|
@@ -2114,8 +2114,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
2114
2114
|
&& existing.nativeSessionId !== input.session.nativeSessionId
|
|
2115
2115
|
&& existing.status === "active";
|
|
2116
2116
|
if (!defersConversationReplacement && (existing?.nativeSessionId !== input.session.nativeSessionId || existing.status !== "active")) {
|
|
2117
|
+
// Preparation reactivates the fixed native identity, not its name.
|
|
2118
|
+
// Preserve the stored title even for an ended Session; only the
|
|
2119
|
+
// naming receipt may advance it on resume.
|
|
2120
|
+
const { title, ...preparedSession } = input.session;
|
|
2117
2121
|
saveTaskSession(store, role, {
|
|
2118
|
-
...
|
|
2122
|
+
...preparedSession,
|
|
2123
|
+
...(existing?.nativeSessionId !== input.session.nativeSessionId && title !== undefined
|
|
2124
|
+
? { title } : {}),
|
|
2119
2125
|
nativeSessionId: input.session.nativeSessionId
|
|
2120
2126
|
}, "active", input.now);
|
|
2121
2127
|
}
|
|
@@ -2180,7 +2186,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2180
2186
|
recordLaunchedRuntimeNativeSession(input, assertCurrent, now = new Date()) {
|
|
2181
2187
|
return this.store.transaction((store) => {
|
|
2182
2188
|
assertCurrent();
|
|
2183
|
-
|
|
2189
|
+
let session = input.owner.scope === "task"
|
|
2184
2190
|
? recordTaskRuntimeNativeSession(store, {
|
|
2185
2191
|
taskId: input.owner.taskId,
|
|
2186
2192
|
roleName: input.owner.roleName,
|
|
@@ -2196,6 +2202,20 @@ export class FileSchedulerStoreAdapter {
|
|
|
2196
2202
|
nativeSessionId: input.nativeSessionId,
|
|
2197
2203
|
effective: input.effective
|
|
2198
2204
|
}, now);
|
|
2205
|
+
if (input.generatedTitle !== undefined && (session.title === undefined
|
|
2206
|
+
|| session.title === session.generatedTitle || session.title === input.generatedTitle)) {
|
|
2207
|
+
const set = input.owner.scope === "task"
|
|
2208
|
+
? store.getTaskRoleSessionSet(input.owner.taskId, input.owner.roleName)
|
|
2209
|
+
: store.getGlobalRoleSessionSet(input.owner.roleName);
|
|
2210
|
+
const titled = recordRoleAgentSession(set, {
|
|
2211
|
+
...session, title: input.generatedTitle, generatedTitle: input.generatedTitle
|
|
2212
|
+
}, now);
|
|
2213
|
+
if (titled.owner.scope === "task")
|
|
2214
|
+
store.saveTaskRoleSessionSet(titled);
|
|
2215
|
+
else
|
|
2216
|
+
store.saveGlobalRoleSessionSet(titled);
|
|
2217
|
+
session = titled.sessions[input.agentId];
|
|
2218
|
+
}
|
|
2199
2219
|
if (input.owner.scope === "global") {
|
|
2200
2220
|
let sessions = store.getGlobalRoleSessionSet(input.owner.roleName);
|
|
2201
2221
|
let binding = sessions.providerBinding;
|
|
@@ -69,6 +69,7 @@ export class RuntimeLaunchCoordinator {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
let preflightObserved = beforeHostStart === undefined;
|
|
72
|
+
let generatedTitle;
|
|
72
73
|
const observePreflight = (preflight) => {
|
|
73
74
|
if (preflightObserved || !sameOwner(preflight.owner, request.owner)
|
|
74
75
|
|| preflight.runId !== request.runId
|
|
@@ -80,6 +81,7 @@ export class RuntimeLaunchCoordinator {
|
|
|
80
81
|
}
|
|
81
82
|
assertLaunchCurrent();
|
|
82
83
|
preflightObserved = true;
|
|
84
|
+
generatedTitle = preflight.generatedTitle;
|
|
83
85
|
beforeHostStart?.(preflight);
|
|
84
86
|
};
|
|
85
87
|
const shared = {
|
|
@@ -94,7 +96,9 @@ export class RuntimeLaunchCoordinator {
|
|
|
94
96
|
};
|
|
95
97
|
// Even without a startup-facts consumer, recheck after asynchronous Host
|
|
96
98
|
// validation and immediately before creating the process.
|
|
97
|
-
const callback = beforeHostStart === undefined
|
|
99
|
+
const callback = beforeHostStart === undefined
|
|
100
|
+
? (preflight) => { assertLaunchCurrent(); generatedTitle = preflight.generatedTitle; }
|
|
101
|
+
: observePreflight;
|
|
98
102
|
if (request.mode === "resume" && !request.nativeSessionId) {
|
|
99
103
|
throw new Error("Restoring a Session requires its native session id.");
|
|
100
104
|
}
|
|
@@ -109,11 +113,18 @@ export class RuntimeLaunchCoordinator {
|
|
|
109
113
|
throw new Error("Session host returned facts inconsistent with the requested launch.");
|
|
110
114
|
}
|
|
111
115
|
if (binding.nativeSessionId !== undefined) {
|
|
116
|
+
// A live Host may reuse its Provider without applying the new plan.
|
|
117
|
+
// Codex reports the actual naming effect; other adapters can only name
|
|
118
|
+
// at creation and must not overwrite native user names on resume.
|
|
119
|
+
const recordedTitle = request.adapterId === "codex"
|
|
120
|
+
? binding.appliedGeneratedTitle
|
|
121
|
+
: request.mode === "new" ? generatedTitle : undefined;
|
|
112
122
|
this.sessions.recordLaunchedRuntimeNativeSession({
|
|
113
123
|
owner: request.owner,
|
|
114
124
|
agentId: request.agentId,
|
|
115
125
|
adapterId: request.adapterId,
|
|
116
126
|
nativeSessionId: binding.nativeSessionId,
|
|
127
|
+
...(recordedTitle === undefined ? {} : { generatedTitle: recordedTitle }),
|
|
117
128
|
effective
|
|
118
129
|
}, assertLaunchCurrent, this.options.now?.() ?? new Date());
|
|
119
130
|
}
|
|
@@ -111,6 +111,8 @@ export function recordRoleAgentSession(set, input, now) {
|
|
|
111
111
|
adapterId,
|
|
112
112
|
nativeSessionId,
|
|
113
113
|
...optionalSessionText("title", input.title ?? continuing?.title),
|
|
114
|
+
...((input.generatedTitle ?? continuing?.generatedTitle) === undefined ? {}
|
|
115
|
+
: { generatedTitle: input.generatedTitle ?? continuing?.generatedTitle }),
|
|
114
116
|
...optionalSessionText("preview", input.preview ?? continuing?.preview),
|
|
115
117
|
policy: input.policy,
|
|
116
118
|
effective: continuing?.effective ?? effective,
|
|
@@ -567,6 +569,11 @@ export function validateRoleAgentSession(session, expectedAgentId = session.agen
|
|
|
567
569
|
throw new Error(`Role Agent session effective identity is inconsistent: ${agentId}.`);
|
|
568
570
|
}
|
|
569
571
|
requireText(session.nativeSessionId, "Native session id");
|
|
572
|
+
if (session.generatedTitle !== undefined
|
|
573
|
+
&& (typeof session.generatedTitle !== "string" || session.generatedTitle.length > 80
|
|
574
|
+
|| optionalSessionText("title", session.generatedTitle).title !== session.generatedTitle)) {
|
|
575
|
+
throw new Error("Role Agent generated title is invalid.");
|
|
576
|
+
}
|
|
570
577
|
if (session.title !== undefined
|
|
571
578
|
&& optionalSessionText("title", session.title).title !== session.title) {
|
|
572
579
|
throw new Error("Role Agent session title is invalid.");
|
|
@@ -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 {
|
|
20
|
+
import { globalRoleSessionTitle, 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";
|
|
@@ -220,7 +220,11 @@ export class FileRoleLaunchPlanner {
|
|
|
220
220
|
// attempt was rejected, cancelled, or completed.
|
|
221
221
|
assertProviderConversationReplaceable(sessionSet.providerBinding);
|
|
222
222
|
}
|
|
223
|
-
return this.#compile(role, input, { scope: "task", taskId: task.id }, resolveTaskRoleSessionTitle(input.mode === "resume" ? existing?.title : undefined, task, role.name
|
|
223
|
+
return this.#compile(role, input, { scope: "task", taskId: task.id }, resolveTaskRoleSessionTitle(input.mode === "resume" ? existing?.title : undefined, task, role.name, {
|
|
224
|
+
executionAuthority: (input.mode === "resume" ? existing?.effective : effective)?.executionAuthority,
|
|
225
|
+
instanceCode: role.instanceCode,
|
|
226
|
+
generatedTitle: existing?.generatedTitle
|
|
227
|
+
}), input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, effective, {
|
|
224
228
|
purpose
|
|
225
229
|
});
|
|
226
230
|
}
|
|
@@ -249,9 +253,12 @@ export class FileRoleLaunchPlanner {
|
|
|
249
253
|
if (input.mode === "new" && sessionSet?.providerBinding != null) {
|
|
250
254
|
assertProviderConversationReplaceable(sessionSet.providerBinding);
|
|
251
255
|
}
|
|
252
|
-
return this.#compile(role, input, { scope: "global" }, input.mode === "new"
|
|
253
|
-
?
|
|
254
|
-
:
|
|
256
|
+
return this.#compile(role, input, { scope: "global" }, input.mode === "new"
|
|
257
|
+
? globalRoleSessionTitle(role.name, this.#now(), this.store.getConfig().timeZone, role.instanceCode)
|
|
258
|
+
: existing?.generatedTitle !== undefined && existing.title === existing.generatedTitle
|
|
259
|
+
? globalRoleSessionTitle(role.name, new Date(existing.createdAt), this.store.getConfig().timeZone, role.instanceCode)
|
|
260
|
+
?? `Yui ${role.name}`
|
|
261
|
+
: undefined, input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, effective, { purpose: "execution" });
|
|
255
262
|
}
|
|
256
263
|
#compile(role, input, owner, sessionTitle, knownNativeSessionId, effective, sessionPolicy) {
|
|
257
264
|
const launchRole = effectiveRoleForLaunch(role, effective);
|
|
@@ -385,7 +392,9 @@ export class FileRoleLaunchPlanner {
|
|
|
385
392
|
agent,
|
|
386
393
|
config: effectiveConfig,
|
|
387
394
|
workspace: agentWorkspace,
|
|
388
|
-
|
|
395
|
+
// CLI --name has no ownership check on resume. Only the Codex native
|
|
396
|
+
// metadata path below may rename an existing conversation.
|
|
397
|
+
...(sessionTitle === undefined || postCreateOperatorTitle || input.mode === "resume"
|
|
389
398
|
? {}
|
|
390
399
|
: { sessionTitle }),
|
|
391
400
|
...sessionContext
|
|
@@ -510,6 +519,9 @@ export class FileRoleLaunchPlanner {
|
|
|
510
519
|
mode: "resume",
|
|
511
520
|
nativeSessionId: requireText(providerNativeSessionId, "Managed Provider restore native session id"),
|
|
512
521
|
...(sessionTitle === undefined ? {} : { sessionTitle }),
|
|
522
|
+
...(existingSession?.generatedTitle !== undefined
|
|
523
|
+
&& existingSession.title === existingSession.generatedTitle
|
|
524
|
+
? { previousSessionTitle: existingSession.generatedTitle } : {}),
|
|
513
525
|
...(managedCompiled.codexThread === undefined
|
|
514
526
|
? {}
|
|
515
527
|
: { codexThread: managedCompiled.codexThread }),
|
|
@@ -594,6 +606,10 @@ export class FileRoleLaunchPlanner {
|
|
|
594
606
|
},
|
|
595
607
|
launch: ordinaryConversationLaunch,
|
|
596
608
|
session,
|
|
609
|
+
...(sessionTitle !== undefined && (input.mode === "new"
|
|
610
|
+
|| existingSession?.title === undefined
|
|
611
|
+
|| existingSession.title === existingSession.generatedTitle)
|
|
612
|
+
? { generatedTitle: sessionTitle } : {}),
|
|
597
613
|
...(sessionTitle === undefined ? {} : { sessionTitle })
|
|
598
614
|
};
|
|
599
615
|
}
|
|
@@ -54,6 +54,7 @@ export function renderRoleDetails(title, role, input) {
|
|
|
54
54
|
...(!isEnabled(role) ? [` Availability ${roleDisabledReason(role)}`] : []),
|
|
55
55
|
` Active Agent ${role.activeAgentId}`,
|
|
56
56
|
` Workspace ${role.workspace}`,
|
|
57
|
+
` Instance code ${role.instanceCode ?? "-"} (display only; next Session launch)`,
|
|
57
58
|
...("taskId" in role ? [
|
|
58
59
|
` Desired environment ${role.executionEnvironment === undefined
|
|
59
60
|
? "managed workspace"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function validateInstanceCode(value) {
|
|
2
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 24
|
|
3
|
+
|| !/^[\p{L}\p{N}\p{M}_-]+$/u.test(value)) {
|
|
4
|
+
throw new Error("Instance code must be 1–24 UTF-16 units of Unicode letters, numbers, marks, '_' or '-'; omit '@'.");
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
package/dist/role/role.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { validateInstanceCode } from "./instanceCode.js";
|
|
2
3
|
import { assertRoleEnabled, validateEnabled } from "./availability.js";
|
|
3
4
|
import { requireKnownFields } from "../domain/validation.js";
|
|
4
5
|
import { jobScopeKey } from "../job/jobDefinition.js";
|
|
@@ -49,6 +50,7 @@ export function copyGlobalRoleToTaskRole(globalRole, taskId, now, name = globalR
|
|
|
49
50
|
const timestamp = now.toISOString();
|
|
50
51
|
return validateTaskRole({
|
|
51
52
|
...cloneProfile(globalRole),
|
|
53
|
+
...(globalRole.instanceCode === undefined ? {} : { instanceCode: globalRole.instanceCode }),
|
|
52
54
|
schemaVersion: 1,
|
|
53
55
|
enabled: true,
|
|
54
56
|
launchRevision: 1,
|
|
@@ -75,6 +77,8 @@ export function updateGlobalRole(role, patch, now) {
|
|
|
75
77
|
updatedAt: now.toISOString()
|
|
76
78
|
};
|
|
77
79
|
clearProfileFields(updated, patch);
|
|
80
|
+
if (Object.hasOwn(patch, "instanceCode") && patch.instanceCode === undefined)
|
|
81
|
+
delete updated.instanceCode;
|
|
78
82
|
updated.launchRevision = isDeepStrictEqual(desiredBefore, desiredLaunchProjection(updated))
|
|
79
83
|
? role.launchRevision
|
|
80
84
|
: role.launchRevision + 1;
|
|
@@ -94,6 +98,8 @@ export function updateRole(role, patch, now) {
|
|
|
94
98
|
updated.executionEnvironment = cloneJson(patch.executionEnvironment);
|
|
95
99
|
}
|
|
96
100
|
clearProfileFields(updated, patch);
|
|
101
|
+
if (Object.hasOwn(patch, "instanceCode") && patch.instanceCode === undefined)
|
|
102
|
+
delete updated.instanceCode;
|
|
97
103
|
updated.launchRevision = isDeepStrictEqual(desiredBefore, desiredLaunchProjection(updated))
|
|
98
104
|
? role.launchRevision
|
|
99
105
|
: role.launchRevision + 1;
|
|
@@ -222,6 +228,7 @@ function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile,
|
|
|
222
228
|
const timestamp = now.toISOString();
|
|
223
229
|
return {
|
|
224
230
|
...cloneProfile(profile),
|
|
231
|
+
...(profile.instanceCode === undefined ? {} : { instanceCode: validateInstanceCode(profile.instanceCode) }),
|
|
225
232
|
enabled: true,
|
|
226
233
|
launchRevision: 1,
|
|
227
234
|
defaultAccess,
|
|
@@ -234,6 +241,8 @@ function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile,
|
|
|
234
241
|
};
|
|
235
242
|
}
|
|
236
243
|
function validateRoleOwner(role) {
|
|
244
|
+
if (role.instanceCode !== undefined)
|
|
245
|
+
validateInstanceCode(role.instanceCode);
|
|
237
246
|
validateEnabled(role.enabled);
|
|
238
247
|
if (role.schemaVersion !== 1) {
|
|
239
248
|
throw new Error("Role schema version is invalid.");
|
|
@@ -303,6 +312,7 @@ function assertSessionOwnerMatchesRole(role, sessions) {
|
|
|
303
312
|
}
|
|
304
313
|
function cloneRolePatch(patch) {
|
|
305
314
|
return {
|
|
315
|
+
...(patch.instanceCode === undefined ? {} : { instanceCode: validateInstanceCode(patch.instanceCode) }),
|
|
306
316
|
...(patch.enabled === undefined ? {} : { enabled: patch.enabled }),
|
|
307
317
|
...(patch.name === undefined ? {} : { name: requireSafeIdentity(patch.name, "Role name") }),
|
|
308
318
|
...(patch.activeAgentId === undefined
|
|
@@ -81,6 +81,7 @@ class BuiltinAgentEndpoint {
|
|
|
81
81
|
driver;
|
|
82
82
|
configuration;
|
|
83
83
|
get nativeAccess() { return this.driver.nativeAccess; }
|
|
84
|
+
get appliedGeneratedTitle() { return this.driver.appliedGeneratedTitle; }
|
|
84
85
|
get ownedProcessId() { return this.driver.ownedProcessId; }
|
|
85
86
|
get nativeAccountHome() { return this.driver.nativeAccountHome; }
|
|
86
87
|
capabilities;
|
|
@@ -120,7 +120,13 @@ export async function runAgentHost(input) {
|
|
|
120
120
|
const recentSteerAttempts = new Map();
|
|
121
121
|
await replayExitOutbox(input.home);
|
|
122
122
|
const updateSnapshot = (next) => {
|
|
123
|
-
|
|
123
|
+
const { appliedGeneratedTitle: _oldTitle, ...rest } = next;
|
|
124
|
+
snapshot = validateSnapshot({
|
|
125
|
+
...rest,
|
|
126
|
+
...(session?.appliedGeneratedTitle === undefined ? {} : {
|
|
127
|
+
appliedGeneratedTitle: session.appliedGeneratedTitle
|
|
128
|
+
})
|
|
129
|
+
});
|
|
124
130
|
};
|
|
125
131
|
const authorityFields = () => ({
|
|
126
132
|
...(session === undefined ? {} : { endpointImplementation: session.configuration.implementation }),
|
|
@@ -374,6 +380,8 @@ export async function runAgentHost(input) {
|
|
|
374
380
|
&& currentPayload.environment.YUI_ROLE === "operator")
|
|
375
381
|
? {}
|
|
376
382
|
: { sessionTitle: previousControl.sessionTitle }),
|
|
383
|
+
...(previousControl.previousSessionTitle === undefined
|
|
384
|
+
? {} : { previousSessionTitle: previousControl.previousSessionTitle }),
|
|
377
385
|
codexThread: disconnectedSession.configuration.threadOptions,
|
|
378
386
|
endpointImplementation: disconnectedSession.configuration.implementation,
|
|
379
387
|
...(ownedTurn === undefined ? {} : { ownedTurn }),
|
|
@@ -167,6 +167,9 @@ function validateProviderControl(control) {
|
|
|
167
167
|
throw new Error("Agent Host Provider session title is invalid.");
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
+
if (control.previousSessionTitle !== undefined) {
|
|
171
|
+
text(control.previousSessionTitle, "previousSessionTitle");
|
|
172
|
+
}
|
|
170
173
|
validateProviderAuthorityFence(control.authority);
|
|
171
174
|
}
|
|
172
175
|
function validateCodexThreadOptions(options) {
|
|
@@ -15,6 +15,9 @@ export function createRuntimeBinding(input) {
|
|
|
15
15
|
...(input.nativeSessionId === undefined
|
|
16
16
|
? {}
|
|
17
17
|
: { nativeSessionId: requireText(input.nativeSessionId, "Native session id") }),
|
|
18
|
+
...(input.appliedGeneratedTitle === undefined ? {} : {
|
|
19
|
+
appliedGeneratedTitle: requireText(input.appliedGeneratedTitle, "Applied generated title")
|
|
20
|
+
}),
|
|
18
21
|
...(input.providerAuthority === undefined
|
|
19
22
|
? {}
|
|
20
23
|
: { providerAuthority: validateProviderAuthorityFence(input.providerAuthority) })
|
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import { resolveTimeZone } from "../config/timeZone.js";
|
|
2
|
+
import { validateInstanceCode } from "../role/instanceCode.js";
|
|
2
3
|
const TITLE_SEPARATOR = " · ";
|
|
3
4
|
export const MAX_SESSION_TITLE_LENGTH = 80;
|
|
4
5
|
const TASK_TITLE_MAX_LENGTH = 20;
|
|
5
|
-
export function taskRoleSessionTitle(task, roleName) {
|
|
6
|
-
const prefix = `Yui ${roleLabel(roleName)} ${normalizeSegment(task.id)}
|
|
6
|
+
export function taskRoleSessionTitle(task, roleName, options = {}) {
|
|
7
|
+
const prefix = `Yui ${roleLabel(roleName)} ${normalizeSegment(task.id)}`
|
|
8
|
+
+ (roleName === "leader" && options.executionAuthority === "planning" ? " Draft" : "")
|
|
9
|
+
+ instanceSegment(options.instanceCode);
|
|
7
10
|
const title = displayTitle(normalizeSegment(task.title), TASK_TITLE_MAX_LENGTH);
|
|
8
11
|
const full = `${prefix}${TITLE_SEPARATOR}${title}`;
|
|
9
12
|
if (full.length <= MAX_SESSION_TITLE_LENGTH)
|
|
10
13
|
return full;
|
|
11
14
|
if (prefix.length + TITLE_SEPARATOR.length + 1 > MAX_SESSION_TITLE_LENGTH) {
|
|
12
|
-
|
|
15
|
+
throw new Error("Session title identity exceeds the maximum length; shorten the Role name or instance code.");
|
|
13
16
|
}
|
|
14
|
-
const titleLength = MAX_SESSION_TITLE_LENGTH - prefix.length - TITLE_SEPARATOR.length
|
|
17
|
+
const titleLength = MAX_SESSION_TITLE_LENGTH - prefix.length - TITLE_SEPARATOR.length;
|
|
15
18
|
return `${prefix}${TITLE_SEPARATOR}${displayTitle(title, Math.max(titleLength, 1))}`;
|
|
16
19
|
}
|
|
17
|
-
export function resolveTaskRoleSessionTitle(existingTitle, task, roleName) {
|
|
20
|
+
export function resolveTaskRoleSessionTitle(existingTitle, task, roleName, options = {}) {
|
|
18
21
|
if (existingTitle !== undefined
|
|
19
22
|
&& existingTitle.length > 0
|
|
20
23
|
&& existingTitle.length <= MAX_SESSION_TITLE_LENGTH
|
|
21
|
-
&& !/[\r\n\0]/u.test(existingTitle)
|
|
24
|
+
&& !/[\r\n\0]/u.test(existingTitle)
|
|
25
|
+
&& existingTitle !== options.generatedTitle) {
|
|
22
26
|
return existingTitle;
|
|
23
27
|
}
|
|
24
|
-
return taskRoleSessionTitle(task, roleName);
|
|
28
|
+
return taskRoleSessionTitle(task, roleName, options);
|
|
25
29
|
}
|
|
26
|
-
export function operatorSessionTitle(now, configuredTimeZone) {
|
|
30
|
+
export function operatorSessionTitle(now, configuredTimeZone, instanceCode) {
|
|
27
31
|
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) {
|
|
28
32
|
throw new TypeError("Operator session title date is invalid.");
|
|
29
33
|
}
|
|
@@ -35,7 +39,23 @@ export function operatorSessionTitle(now, configuredTimeZone) {
|
|
|
35
39
|
if (parts.month === undefined || parts.day === undefined) {
|
|
36
40
|
throw new TypeError("Operator session title date is unavailable.");
|
|
37
41
|
}
|
|
38
|
-
|
|
42
|
+
const prefix = instanceCode === undefined
|
|
43
|
+
? `Yui${TITLE_SEPARATOR}Operator`
|
|
44
|
+
: `Yui Operator${instanceSegment(instanceCode)}`;
|
|
45
|
+
return `${prefix}${TITLE_SEPARATOR}${parts.month}${parts.day}`;
|
|
46
|
+
}
|
|
47
|
+
export function globalRoleSessionTitle(roleName, now, configuredTimeZone, instanceCode) {
|
|
48
|
+
if (roleName === "operator")
|
|
49
|
+
return operatorSessionTitle(now, configuredTimeZone, instanceCode);
|
|
50
|
+
if (instanceCode === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
const title = `Yui ${roleLabel(roleName)}${instanceSegment(instanceCode)}`;
|
|
53
|
+
if (title.length > MAX_SESSION_TITLE_LENGTH)
|
|
54
|
+
throw new Error("Session title identity exceeds the maximum length.");
|
|
55
|
+
return title;
|
|
56
|
+
}
|
|
57
|
+
function instanceSegment(value) {
|
|
58
|
+
return value === undefined ? "" : ` @${validateInstanceCode(value)}`;
|
|
39
59
|
}
|
|
40
60
|
function displayTitle(value, maxLength) {
|
|
41
61
|
if (value.length <= maxLength)
|
|
@@ -384,6 +384,7 @@ class CodexStructuredProviderSession {
|
|
|
384
384
|
onActivity;
|
|
385
385
|
mirror;
|
|
386
386
|
nativeAccountHome;
|
|
387
|
+
appliedGeneratedTitle;
|
|
387
388
|
nativeAccess;
|
|
388
389
|
#turnAttempts = new Map();
|
|
389
390
|
adapterId = "codex";
|
|
@@ -445,12 +446,20 @@ class CodexStructuredProviderSession {
|
|
|
445
446
|
throw error;
|
|
446
447
|
}
|
|
447
448
|
}
|
|
448
|
-
|
|
449
|
+
const threadMetadata = nativeConfiguration.thread;
|
|
450
|
+
const nativeTitle = typeof threadMetadata === "object" && threadMetadata !== null
|
|
451
|
+
&& !Array.isArray(threadMetadata) ? threadMetadata.name : undefined;
|
|
452
|
+
// A native rename belongs to the user. Missing metadata is not evidence
|
|
453
|
+
// that an old title is still ours; leave it alone on resume.
|
|
454
|
+
let appliedGeneratedTitle;
|
|
455
|
+
if (control.sessionTitle !== undefined && (control.mode === "new"
|
|
456
|
+
|| (control.previousSessionTitle !== undefined && nativeTitle === control.previousSessionTitle))) {
|
|
449
457
|
try {
|
|
450
458
|
await runtime.setConversationName({
|
|
451
459
|
conversationId,
|
|
452
460
|
name: control.sessionTitle
|
|
453
461
|
});
|
|
462
|
+
appliedGeneratedTitle = control.sessionTitle;
|
|
454
463
|
}
|
|
455
464
|
catch (error) {
|
|
456
465
|
onDiagnostic?.({
|
|
@@ -465,6 +474,7 @@ class CodexStructuredProviderSession {
|
|
|
465
474
|
}
|
|
466
475
|
const session = new CodexStructuredProviderSession(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, mirror, optionalId(initialized.codexHome));
|
|
467
476
|
session.nativeAccess = createCodexNativeAccess(conversationId, initialized, channel, openingMessages, nativeConfiguration);
|
|
477
|
+
session.appliedGeneratedTitle = appliedGeneratedTitle;
|
|
468
478
|
session.#activeTurnId = resumedActiveTurnId;
|
|
469
479
|
const ownedTurn = control.kind === "restore" ? control.ownedTurn : undefined;
|
|
470
480
|
session.#clientOwnedTurnId = ownedTurn?.turnId;
|
|
@@ -246,6 +246,7 @@ export class TmuxSessionHost {
|
|
|
246
246
|
agentId: request.agentId,
|
|
247
247
|
adapterId: request.adapterId,
|
|
248
248
|
effective: request.effective,
|
|
249
|
+
...(planned.generatedTitle === undefined ? {} : { generatedTitle: planned.generatedTitle }),
|
|
249
250
|
...(planned.launch.env.YUI_SESSION_TITLE === undefined
|
|
250
251
|
? {}
|
|
251
252
|
: { sessionTitle: planned.launch.env.YUI_SESSION_TITLE }),
|
|
@@ -436,6 +437,9 @@ export class TmuxSessionHost {
|
|
|
436
437
|
roleName: request.owner.roleName
|
|
437
438
|
}),
|
|
438
439
|
hostCreated,
|
|
440
|
+
...(providerSnapshot?.appliedGeneratedTitle === undefined ? {} : {
|
|
441
|
+
appliedGeneratedTitle: providerSnapshot.appliedGeneratedTitle
|
|
442
|
+
}),
|
|
439
443
|
...((providerSnapshot?.nativeSessionId ?? nativeSessionId) === undefined
|
|
440
444
|
? {}
|
|
441
445
|
: { nativeSessionId: providerSnapshot?.nativeSessionId ?? nativeSessionId }),
|
|
@@ -276,6 +276,16 @@ const MINOR_UPGRADES = Object.freeze([{
|
|
|
276
276
|
// No payload rewriting or historical revision invention. A newly opened
|
|
277
277
|
// reader starts empty; zero is the initial token until the first mutation.
|
|
278
278
|
sql: TASK_READ_REVISION_SCHEMA_SQL
|
|
279
|
+
}, {
|
|
280
|
+
fromVersion: "1.24",
|
|
281
|
+
toVersion: "1.25",
|
|
282
|
+
name: "role-instance-code-session-titles",
|
|
283
|
+
introducedIn: "next",
|
|
284
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
285
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
286
|
+
// Optional Role presentation and generated-title provenance. Never infer
|
|
287
|
+
// ownership of historical titles, rewrite frozen profiles, or fill defaults.
|
|
288
|
+
sql: ""
|
|
279
289
|
}]);
|
|
280
290
|
export function storageMinorUpgradePlan(from) {
|
|
281
291
|
if (from === CURRENT_STORAGE_VERSION)
|
package/docs/release-workflow.md
CHANGED
|
@@ -51,6 +51,35 @@ merely to avoid a version decision. Package versions remain distinct from protoc
|
|
|
51
51
|
and persistent-storage contracts: their compatibility checks and required migrations
|
|
52
52
|
are unchanged, and the storage rules below do not authorize a package release.
|
|
53
53
|
|
|
54
|
+
### 2.20.0 compatibility and migration notes
|
|
55
|
+
|
|
56
|
+
This minor adds independent optional Role instance codes (`--instance-code` /
|
|
57
|
+
`--clear-instance-code`) and a `Draft` marker for Leader Sessions with planning
|
|
58
|
+
authority. Operator-only configuration does not propagate to Leader or other
|
|
59
|
+
Roles. Task Role template copies remain independent; codes are presentation,
|
|
60
|
+
not identity, authorization or launch-configuration drift. Full lowercase Task
|
|
61
|
+
IDs remain unchanged. See [Role configuration](roles-and-configuration.md).
|
|
62
|
+
|
|
63
|
+
New Sessions use the configured code. Normal Codex Host launches/resumes can refresh
|
|
64
|
+
confirmed Yui-owned titles without forcing a stop/replacement. Historical untagged
|
|
65
|
+
titles and recorded custom names are preserved. Codex resume naming requires
|
|
66
|
+
the native name to match the previous generated title; missing metadata and
|
|
67
|
+
user renames are left untouched. Title provenance advances only after a successful
|
|
68
|
+
native naming receipt, not pre-start registration or live Host reuse. Other
|
|
69
|
+
Providers receive generated names only on creation, never on resume. Provider
|
|
70
|
+
naming remains best effort.
|
|
71
|
+
|
|
72
|
+
Storage advances **1.24 → 1.25** through an additive, no-rewrite transition for
|
|
73
|
+
optional Role code and Session title provenance. Older supported Homes follow
|
|
74
|
+
the centralized contiguous chain; do not open an upgraded Home with older
|
|
75
|
+
software. Controller/Host protocols remain v1. Publication does not authorize
|
|
76
|
+
installing the release, migrating a production Home, restarting shared services,
|
|
77
|
+
replacing Sessions or choosing codes for existing Roles.
|
|
78
|
+
|
|
79
|
+
Release gates require exact-merge Core CI and the same runtime tarball's
|
|
80
|
+
fresh-install matrix before npm `latest` publication. These deterministic
|
|
81
|
+
checks do not establish real-model behavior.
|
|
82
|
+
|
|
54
83
|
### 2.19.0 compatibility and migration notes
|
|
55
84
|
|
|
56
85
|
This minor adds message backlog and blocked-delivery monitoring with Operator
|
|
@@ -36,6 +36,28 @@ Agent 选择一个预先声明的计划,设施从持久状态驱动该计划
|
|
|
36
36
|
不能将不兼容冒称为兼容,也不为回避版本选择新增历史兼容机制。包版本与协议、
|
|
37
37
|
持久存储契约仍然独立:兼容性检查和必要迁移要求不变,下文存储规则不授予发版权限。
|
|
38
38
|
|
|
39
|
+
### 2.20.0 兼容性及迁移说明
|
|
40
|
+
|
|
41
|
+
本 minor 增加按 Role 独立可选的实例码(`--instance-code` /
|
|
42
|
+
`--clear-instance-code`),以及持有 planning 权限的 Leader Session 的 `Draft`
|
|
43
|
+
标识。仅设置 Operator 不会传播到 Leader 或其他 Role;Task Role 模板副本保持
|
|
44
|
+
独立。实例码仅供展示,不改变身份、权限、启动配置版本或完整小写 Task ID。
|
|
45
|
+
详见 [Role 配置](roles-and-configuration.zh-CN.md)。
|
|
46
|
+
|
|
47
|
+
新会话采用配置,正常 Codex Host 启动/恢复可刷新确认由 Yui 管理的标题,不强停或替换会话。
|
|
48
|
+
旧的无来源标记标题和已记录的用户标题保留。Codex 恢复时仅在原生名字仍匹配上次
|
|
49
|
+
生成标题时改名;原生信息缺失或用户已改名则跳过。标题来源仅在原生命名成功回执后
|
|
50
|
+
推进,不因预启动登记或存活 Host 复用而变化。其他 Provider 仅在新建时传入生成名,
|
|
51
|
+
恢复时不传入。Provider 命名仍为尽力能力。
|
|
52
|
+
|
|
53
|
+
存储通过中央连续迁移从 **1.24 → 1.25**,只增加可选实例码和标题来源,
|
|
54
|
+
不改写历史记录。较早的受支持 Home 沿既有迁移链升级,不可用旧程序打开升级后的
|
|
55
|
+
Home。Controller/Host 协议仍为 v1。发布不授权安装升级、迁移生产 Home、
|
|
56
|
+
重启共享服务、替换会话或为现有 Role 选择实例码。
|
|
57
|
+
|
|
58
|
+
发布门禁要求准确合入 commit 的 Core CI 和同一 runtime tarball 的全平台
|
|
59
|
+
fresh-install 矩阵通过后才向 npm `latest` 发布;这些确定性验证不证明真实模型行为。
|
|
60
|
+
|
|
39
61
|
### 2.19.0 兼容性及迁移说明
|
|
40
62
|
|
|
41
63
|
本 minor 新增消息积压、投递阻塞监测及 Operator 告警(PR #492)。观测区分正常执行、
|
|
@@ -18,6 +18,55 @@ requiring a same-named global Role. A global Role is a creation template only
|
|
|
18
18
|
when the requested Task Role does not exist. Availability, producer separation
|
|
19
19
|
and the frozen review candidate still apply.
|
|
20
20
|
|
|
21
|
+
## Optional instance codes and Session titles
|
|
22
|
+
|
|
23
|
+
Each Role can independently set a display-only instance code. For example,
|
|
24
|
+
configure only Operator, leaving Leader and all other Roles unset:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
yui config role update operator --instance-code dev-bj
|
|
28
|
+
yui config role show operator
|
|
29
|
+
yui config role update operator --clear-instance-code
|
|
30
|
+
yui task role update task-159 leader --instance-code dev-sg
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`add` also accepts `--instance-code`. Codes accept 1–24 UTF-16 units of Unicode
|
|
34
|
+
letters, numbers, combining marks, hyphen and underscore; omit `@`. Whitespace,
|
|
35
|
+
controls, escapes and punctuation are rejected, not silently normalized.
|
|
36
|
+
There is no Home-wide fallback. A Global Role's code is copied when that template
|
|
37
|
+
creates a Task Role; later template changes do not rewrite the copy. Agent
|
|
38
|
+
Profiles do not supply display codes. Use Task Role update/clear for an existing
|
|
39
|
+
copy. Codes do not alter launch revisions, permissions, IDs or message routing.
|
|
40
|
+
Choose recognizable values; codes are not globally unique identities. Use
|
|
41
|
+
Session inspection and the actual Home/native identity when resolving ambiguity.
|
|
42
|
+
|
|
43
|
+
Generated Task titles use `Yui Leader task-159 Draft @dev-sg · Task name`.
|
|
44
|
+
`Draft` means the Leader Session has planning authority, including a retained
|
|
45
|
+
planning Session after activation; delivery Sessions omit it. Other Role names
|
|
46
|
+
and the full lowercase Task ID remain intact. Operator uses
|
|
47
|
+
`Yui Operator @dev-bj · MMDD`; without a code its existing
|
|
48
|
+
`Yui · Operator · MMDD` format remains. The date derives from Session creation
|
|
49
|
+
and the configured timezone. Other Global Roles get a generated title when a
|
|
50
|
+
code is configured. Descriptions are truncated first without splitting surrogate
|
|
51
|
+
pairs; if the identity itself cannot fit the 80-unit title limit, launch fails
|
|
52
|
+
with a bounded diagnostic instead of truncating the identity.
|
|
53
|
+
|
|
54
|
+
Configuration does not rename, stop or recreate a live Session. New Sessions
|
|
55
|
+
use it; a subsequent normal Host launch/resume can refresh confirmed Yui-owned titles,
|
|
56
|
+
not historical records. Existing untagged titles and recorded user names stay
|
|
57
|
+
untouched. Codex applies a resume rename only when its returned native name still
|
|
58
|
+
matches the previous generated title; absent metadata or a user rename prevents
|
|
59
|
+
the rename. Codex records only successfully applied names; live Host reuse,
|
|
60
|
+
skipped naming and naming failures do not advance title provenance. Naming is
|
|
61
|
+
best effort; unsupported Providers or failures do not invalidate the Session.
|
|
62
|
+
Other Providers receive generated names only on creation, never on resume,
|
|
63
|
+
to protect native user renames. No restart is performed to force a naming change.
|
|
64
|
+
On the safe Codex resume path, clearing a custom Global Role's code restores
|
|
65
|
+
`Yui role`; clearing Operator's code restores its unconfigured format.
|
|
66
|
+
|
|
67
|
+
Storage 1.24 → 1.25 adds optional code/title provenance without rewriting
|
|
68
|
+
historical payloads or inferring who named an old Session.
|
|
69
|
+
|
|
21
70
|
## Enable and disable
|
|
22
71
|
|
|
23
72
|
Availability is configuration, separate from active Agent selection, Session
|
|
@@ -15,6 +15,44 @@ Task Role 是 Task 局部的;global Role 提供已配置的默认值和全局
|
|
|
15
15
|
的 global Role。只有当请求的 Task Role 不存在时,global Role 才作为创建模板。
|
|
16
16
|
可用性、producer 分离和冻结的审查候选仍然适用。
|
|
17
17
|
|
|
18
|
+
## 可选实例码与会话标题
|
|
19
|
+
|
|
20
|
+
每个 Role 独立配置仅用于展示的实例码。例如只设置 Operator,其他角色留空:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
yui config role update operator --instance-code dev-bj
|
|
24
|
+
yui config role show operator
|
|
25
|
+
yui config role update operator --clear-instance-code
|
|
26
|
+
yui task role update task-159 leader --instance-code dev-sg
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`add` 也接受 `--instance-code`。允许 1–24 个 UTF-16 单位的 Unicode 字母、
|
|
30
|
+
数字、组合标记、短横线和下划线,不输入 `@`。空白、控制字符、转义和其他标点
|
|
31
|
+
直接拒绝,不静默清洗。没有 Home 级默认值;Global Role 作为模板创建 Task Role
|
|
32
|
+
时复制其值,之后模板变化不回写。Agent Profile 不携带实例码;既有 Task Role
|
|
33
|
+
通过自己的 update/clear 修改。实例码不改变启动版本、权限、内部 ID 或消息寻址,
|
|
34
|
+
也不保证全局唯一;遇到歧义应查看实际 Home、Session 和原生身份。
|
|
35
|
+
|
|
36
|
+
生成的 Task 标题为 `Yui Leader task-159 Draft @dev-sg · 任务名`。
|
|
37
|
+
`Draft` 根据 Leader Session 的 planning 权限显示;Task 激活后保留的 planning
|
|
38
|
+
会话仍显示 Draft,delivery 会话不显示。保留完整小写 Task ID。
|
|
39
|
+
Operator 为 `Yui Operator @dev-bj · MMDD`;未配置保持
|
|
40
|
+
`Yui · Operator · MMDD`,日期使用会话创建时间和配置时区。
|
|
41
|
+
其他 Global Role 配置实例码后生成标题。80 单位总长度内优先截断任务名,
|
|
42
|
+
不拆开代理对;身份部分无法容纳则明确报错,不静默截断。
|
|
43
|
+
|
|
44
|
+
配置修改不会立即重命名、停止或重建活动会话。新会话采用新配置,后续正常
|
|
45
|
+
Host 启动/恢复可刷新确认由 Yui 管理的标题,不改写历史记录。
|
|
46
|
+
缺少来源标记的旧标题及已记录的用户标题保留。Codex 恢复时只有原生返回的
|
|
47
|
+
名字仍等于上次生成标题才改名;原生信息缺失或用户已改名则跳过。
|
|
48
|
+
Codex 只记录已成功应用的名称;复用存活 Host、命名失败或跳过不推进标题来源。
|
|
49
|
+
Provider 命名为尽力能力;失败或不支持不会使会话失效。其他 Provider 仅在新建时
|
|
50
|
+
传入生成名,恢复时不重新传入名称,以保护原生用户改名;不为改名主动重启。
|
|
51
|
+
在可安全改名的 Codex 恢复路径,清除自定义 Global Role 实例码后恢复
|
|
52
|
+
`Yui role`;Operator 恢复未配置格式。
|
|
53
|
+
|
|
54
|
+
存储 1.24 → 1.25 增加可选实例码和生成标题来源,不回填历史记录或猜测标题所有权。
|
|
55
|
+
|
|
18
56
|
## 启用与停用
|
|
19
57
|
|
|
20
58
|
启停是配置状态,与 Active Agent、Session 是否运行和 Task executionGate 分开:
|
package/docs/storage-baseline.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# Storage baseline 1.0
|
|
4
4
|
|
|
5
|
-
Current storage is 1.
|
|
5
|
+
Current storage is 1.25. The 1.24 → 1.25 transition admits optional Role
|
|
6
|
+
`instanceCode` and Session `generatedTitle` presentation fields without rewriting
|
|
7
|
+
existing records. Untagged historical titles remain user-owned; no code or
|
|
8
|
+
title provenance is inferred. The 1.23 → 1.24 transition adds transaction-maintained
|
|
6
9
|
Task read-cache revisions for the scheduler's nine source tables. Inserts,
|
|
7
10
|
updates and deletions invalidate only the affected Task; rollback also rolls
|
|
8
11
|
back the token. These tokens are derived cache identities, never Task authority
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# 存储基线 1.0
|
|
4
4
|
|
|
5
|
-
当前存储为 1.
|
|
5
|
+
当前存储为 1.25。1.24 → 1.25 增加可选 Role `instanceCode` 和 Session `generatedTitle` 展示字段,不回填历史标题来源或实例码,不改写既有记录。1.23 → 1.24 为调度器的九类来源表增加事务维护的 Task 读取缓存修订号。插入、更新和删除只使对应 Task 的缓存失效,回滚同时回滚修订号。该值仅是派生缓存标识,不是 Task 权限或 Home revision CAS 的替代。历史 payload 不变,旧 Task 在首次修改前使用隐含的零修订号。
|
|
6
6
|
同时增加两个局部索引,支持按精确运行观测身份查找,不再扫描或反序列化历史观测集合。
|
|
7
7
|
|
|
8
8
|
1.22 → 1.23 新增全局 Shell Job、版本化脚本文件及每次运行的配置快照,不改写已有 Job、计划和历史记录。
|
|
@@ -53,12 +53,12 @@ is required.
|
|
|
53
53
|
Both include `test/core` and `test/platform`. For an already built checkout,
|
|
54
54
|
`npm run test:business` runs only `test/core` and `npm run test:platform` runs
|
|
55
55
|
only `test/platform`; neither rebuilds. CI owns one explicit build per job.
|
|
56
|
-
The stable suite owns current storage 1.
|
|
56
|
+
The stable suite owns current storage 1.25 and its declared 1.0 through 1.24 minor
|
|
57
57
|
transition. It contains no undeclared historical format compatibility path.
|
|
58
58
|
|
|
59
59
|
Current coverage includes:
|
|
60
60
|
|
|
61
|
-
1. Fresh storage 1.
|
|
61
|
+
1. Fresh storage 1.25, exact schema/record validation, no initialization over
|
|
62
62
|
unknown data, and rejection of old integer formats without mutation.
|
|
63
63
|
2. Exact-version staging, mismatched-target refusal, same-major contiguous
|
|
64
64
|
minor preflight, explicit maintenance-owner identity across handover, and
|
|
@@ -39,7 +39,7 @@ Yui 是一个单用户本地产品。永久验证保护关键的 happy path 和
|
|
|
39
39
|
两者均包含 `test/core` 和 `test/platform`。已构建的 checkout 可分别运行
|
|
40
40
|
`npm run test:business` 与 `npm run test:platform`,它们不会再次构建。
|
|
41
41
|
CI 每个作业只显式构建一次。
|
|
42
|
-
稳定套件维护当前存储 1.
|
|
42
|
+
稳定套件维护当前存储 1.25 和明确声明的 1.0 至 1.24 连续小版本迁移,
|
|
43
43
|
不包含未声明的历史格式兼容路径。
|
|
44
44
|
|
|
45
45
|
当前覆盖包括:
|