@zq-silk/yui 2.22.0 → 2.25.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 +9 -4
- package/dist/artifacts/artifactCapability.js +6 -6
- package/dist/artifacts/gitArtifactRef.js +10 -10
- package/dist/artifacts/jobArtifact.js +5 -5
- package/dist/artifacts/managedGit.js +15 -12
- package/dist/artifacts/taskArtifactRepository.js +35 -33
- package/dist/cli/commandCatalog.js +2 -2
- package/dist/cli/updateCommand.js +20 -3
- package/dist/cli/updateOrchestrator.js +13 -0
- package/dist/cli/updatePorts.js +27 -0
- package/dist/cli.js +9 -2
- package/dist/commands/configCommands.js +64 -1
- package/dist/commands/projectCommands.js +9 -9
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +1 -1
- package/dist/commands/taskPublicationAdoptCommand.js +1 -1
- package/dist/commands/taskRoleRuntimeStatus.js +4 -0
- package/dist/commands/taskUpstreamCommands.js +1 -1
- package/dist/config/configCatalog.js +3 -0
- package/dist/config/gitConfig.js +20 -0
- package/dist/config/yuiConfig.js +1 -0
- package/dist/context/currentSkills.js +32 -1
- package/dist/context/sessionBootstrapManifest.js +17 -4
- package/dist/context/sessionSkillNotice.js +33 -12
- package/dist/controlPlaneCli.js +4 -4
- package/dist/controller/clientRuntime.js +37 -18
- package/dist/controller/controller.js +16 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +2 -0
- package/dist/controller/hostLifecycle.js +153 -0
- package/dist/controller/hostLifecycleChecks.js +27 -0
- package/dist/controller/hostUpgrade.js +344 -0
- package/dist/controller/jobArtifactControl.js +3 -3
- package/dist/controller/jobDefinitionControl.js +1 -1
- package/dist/controller/runtime.js +67 -16
- package/dist/controller/sessionNotify.js +5 -46
- package/dist/execution/codexThreadNaming.js +54 -43
- package/dist/executor/fileRoleLaunchPlanner.js +18 -3
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/kernel/builtinCapabilities.js +3 -3
- package/dist/repository/gitCommand.js +153 -0
- package/dist/repository/gitCompatibility.js +3 -2
- package/dist/repository/gitWorkspace.js +579 -563
- package/dist/repository/gitWorktreeInventory.js +15 -19
- package/dist/repository/projectRefresh.js +1 -1
- package/dist/repository/taskBaseFreshness.js +1 -4
- package/dist/repository/taskWorkspacePreparer.js +1 -1
- package/dist/runtime/agentEndpoint.js +11 -1
- package/dist/runtime/agentEndpointIdentity.js +2 -0
- package/dist/runtime/agentHost.js +167 -42
- package/dist/runtime/agentHostImplementation.js +3 -1
- package/dist/runtime/codexAppServerRuntime.js +3 -0
- package/dist/runtime/codexInteractiveHost.js +62 -17
- 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/nativeExecutable.js +5 -2
- package/dist/runtime/nativeTitleReceipt.js +31 -0
- package/dist/runtime/nativeTitleSync.js +192 -0
- package/dist/runtime/platform.js +11 -0
- package/dist/runtime/providerRetry.js +4 -4
- package/dist/runtime/structuredProviderHost.js +42 -10
- package/dist/storage/sqliteSchema.js +30 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +7 -1
- package/dist/web/webConversation.js +2 -2
- package/dist/web/webMaterials.js +19 -19
- package/dist/web/webTaskSurface.js +3 -3
- package/dist/workspace/workItemChangeSetManager.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +96 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +70 -1
- package/docs/project-refresh.md +3 -2
- package/docs/project-refresh.zh-CN.md +2 -1
- package/docs/release-workflow.md +86 -2
- package/docs/release-workflow.zh-CN.md +65 -1
- package/docs/roles-and-configuration.md +34 -0
- package/docs/roles-and-configuration.zh-CN.md +27 -0
- package/docs/storage-baseline.md +14 -1
- package/docs/storage-baseline.zh-CN.md +7 -1
- package/docs/testing/verification-levels.md +7 -7
- package/docs/testing/verification-levels.zh-CN.md +6 -6
- package/i18n/README.zh-CN.md +8 -4
- package/package.json +1 -1
- package/skills/yui-runtime/SKILL.md +7 -2
- package/skills/yui-runtime/VERSION +1 -1
- package/dist/runtime/native/darwin-x64/claude-process-owner +0 -0
- package/dist/runtime/native/darwin-x64/process-identity +0 -0
|
@@ -164,16 +164,16 @@ export function completeProviderRetry(binding, turn) {
|
|
|
164
164
|
export function providerRetryAttemptId(retry) {
|
|
165
165
|
return `provider-retry:${retry.chainId}/${retry.transportSequence + 1}`;
|
|
166
166
|
}
|
|
167
|
-
|
|
167
|
+
/** Recovery guidance only. User input is delivered separately, once. */
|
|
168
|
+
export function providerRetryContext(retry) {
|
|
168
169
|
if (retry.failedNativeTurnId === undefined)
|
|
169
|
-
return
|
|
170
|
+
return undefined;
|
|
170
171
|
return [
|
|
171
172
|
"Yui infrastructure recovery, not new user authorization.",
|
|
172
173
|
`Continue the existing input after failed attempt ${retry.failureAttemptId}`,
|
|
173
174
|
`(native Turn ${retry.failedNativeTurnId}; failure ${retry.failureRef}).`,
|
|
174
175
|
"Read current authorized context and the original input. Inspect existing work and receipts first;",
|
|
175
|
-
"continue only unfinished work. Preserve local changes and do not repeat confirmed side effects."
|
|
176
|
-
original
|
|
176
|
+
"continue only unfinished work. Preserve local changes and do not repeat confirmed side effects."
|
|
177
177
|
].join("\n");
|
|
178
178
|
}
|
|
179
179
|
/** The same presentation is consumed by explicit detail reads and Web. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { createNativeTitleSync } from "./nativeTitleSync.js";
|
|
3
4
|
import { Duplex } from "node:stream";
|
|
4
5
|
import { nativeExecutable } from "./nativeExecutable.js";
|
|
5
6
|
import { startMissingCodexSharedDaemon } from "./codexSharedDaemon.js";
|
|
@@ -15,6 +16,7 @@ import { JsonLineChannel, PROVIDER_MESSAGE_MAX_BYTES, terminateProcessGroup } fr
|
|
|
15
16
|
import { PROVIDER_ACCEPT_TIMEOUT_MS } from "./runtimeDeadlines.js";
|
|
16
17
|
import { foldPublicReply } from "./publicReply.js";
|
|
17
18
|
import { createCodexNativeAccess } from "./codexNativeAccess.js";
|
|
19
|
+
import { isNativeContextRecovery } from "./contextRecovery.js";
|
|
18
20
|
const CODEX_PROXY_HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
19
21
|
/**
|
|
20
22
|
* These Session implementations do not expose run-configuration observations.
|
|
@@ -396,7 +398,22 @@ class CodexStructuredProviderSession {
|
|
|
396
398
|
onActivity;
|
|
397
399
|
mirror;
|
|
398
400
|
nativeAccountHome;
|
|
401
|
+
contextEpoch = 0;
|
|
402
|
+
async checkQuiescent() {
|
|
403
|
+
const snapshot = await this.runtime.readConversation(this.conversationId, { includeTurns: false });
|
|
404
|
+
if (snapshot.threadId !== this.conversationId || snapshot.status !== "idle"
|
|
405
|
+
|| snapshot.activeTurnId !== undefined)
|
|
406
|
+
return false;
|
|
407
|
+
const backgrounds = await this.nativeAccess.read("thread/backgroundTerminals/list", {
|
|
408
|
+
threadId: this.conversationId, limit: 1
|
|
409
|
+
});
|
|
410
|
+
if (!Array.isArray(backgrounds.data) || !("nextCursor" in backgrounds)) {
|
|
411
|
+
throw new Error("Native background execution response is invalid.");
|
|
412
|
+
}
|
|
413
|
+
return backgrounds.data.length === 0 && backgrounds.nextCursor === null;
|
|
414
|
+
}
|
|
399
415
|
appliedGeneratedTitle;
|
|
416
|
+
#titleSync;
|
|
400
417
|
nativeAccess;
|
|
401
418
|
#turnAttempts = new Map();
|
|
402
419
|
adapterId = "codex";
|
|
@@ -456,16 +473,6 @@ class CodexStructuredProviderSession {
|
|
|
456
473
|
throw error;
|
|
457
474
|
}
|
|
458
475
|
}
|
|
459
|
-
if (control.sessionTitle !== undefined) {
|
|
460
|
-
// The native title mutation can persist without replying and stall later
|
|
461
|
-
// Goal/Turn requests, even through a separate metadata connection. Keep
|
|
462
|
-
// Yui's launch title, but do not put that optional mutation on a managed
|
|
463
|
-
// Session's execution path. Reconnect must not replay it either.
|
|
464
|
-
// This is an intentional omission, not a failed delivery or unavailable
|
|
465
|
-
// observer. Keep it in the Host log without waking an Agent to recover
|
|
466
|
-
// an operation that was never attempted.
|
|
467
|
-
mirror("stderr", "Native title synchronization was not submitted; Yui retains its Session title without blocking native Goal/Turn control.\n");
|
|
468
|
-
}
|
|
469
476
|
const ownedTurn = control.kind === "restore" ? control.ownedTurn : undefined;
|
|
470
477
|
// Keep notifications buffered during paginated recovery. Otherwise a live
|
|
471
478
|
// terminal can be emitted before open returns and then returned again as a
|
|
@@ -521,6 +528,15 @@ class CodexStructuredProviderSession {
|
|
|
521
528
|
}
|
|
522
529
|
}
|
|
523
530
|
const goal = await runtime.readGoal(conversationId);
|
|
531
|
+
session.#titleSync = createNativeTitleSync(payload, conversationId, session.nativeAccountHome, result => {
|
|
532
|
+
if (result.status === "confirmed")
|
|
533
|
+
session.appliedGeneratedTitle = result.title;
|
|
534
|
+
mirror("stderr", `Native title: ${JSON.stringify(result)}\n`);
|
|
535
|
+
});
|
|
536
|
+
void exit.then(() => session.#titleSync?.close());
|
|
537
|
+
// Fresh Threads may not be persisted until their first accepted input.
|
|
538
|
+
if (control.mode === "resume")
|
|
539
|
+
session.#titleSync.notify();
|
|
524
540
|
return Object.freeze({
|
|
525
541
|
session,
|
|
526
542
|
...(recoveredTerminal === undefined ? {} : { recoveredTerminal }),
|
|
@@ -530,6 +546,8 @@ class CodexStructuredProviderSession {
|
|
|
530
546
|
#observeMessage(message, emit) {
|
|
531
547
|
const method = typeof message.method === "string" ? message.method : "";
|
|
532
548
|
const params = object(message.params) ?? {};
|
|
549
|
+
if (isNativeContextRecovery("codex", this.conversationId, message))
|
|
550
|
+
this.contextEpoch += 1;
|
|
533
551
|
const threadId = optionalId(params.threadId);
|
|
534
552
|
if (emit)
|
|
535
553
|
this.publicReply = foldPublicReply(this.publicReply, this.conversationId, method, params);
|
|
@@ -539,6 +557,13 @@ class CodexStructuredProviderSession {
|
|
|
539
557
|
}
|
|
540
558
|
return undefined;
|
|
541
559
|
}
|
|
560
|
+
if (method === "thread/name/updated") {
|
|
561
|
+
this.#titleSync?.close();
|
|
562
|
+
if (params.threadName !== this.appliedGeneratedTitle)
|
|
563
|
+
this.appliedGeneratedTitle = undefined;
|
|
564
|
+
}
|
|
565
|
+
if (method === "turn/started")
|
|
566
|
+
this.#titleSync?.notify();
|
|
542
567
|
let goal;
|
|
543
568
|
try {
|
|
544
569
|
goal = codexGoalNotification({ method, params });
|
|
@@ -663,6 +688,7 @@ class CodexStructuredProviderSession {
|
|
|
663
688
|
conversationId: this.conversationId,
|
|
664
689
|
attemptId: turn.attemptId,
|
|
665
690
|
text: turn.boundedText,
|
|
691
|
+
...(turn.additionalContext === undefined ? {} : { additionalContext: turn.additionalContext }),
|
|
666
692
|
expectedNoActiveTurn: true,
|
|
667
693
|
...(turn.requireQuiescent ? { requireQuiescent: true } : {}),
|
|
668
694
|
...(turn.expectedFailedNativeTurnId === undefined ? {} : {
|
|
@@ -682,6 +708,7 @@ class CodexStructuredProviderSession {
|
|
|
682
708
|
this.#clientOwnedTurnId = acceptance.turnId;
|
|
683
709
|
this.#clientOwnedAttemptId = turn.attemptId;
|
|
684
710
|
this.#turnAttempts.set(acceptance.turnId, turn.attemptId);
|
|
711
|
+
this.#titleSync?.notify();
|
|
685
712
|
return Object.freeze({
|
|
686
713
|
attemptId: turn.attemptId,
|
|
687
714
|
conversationId: this.conversationId,
|
|
@@ -712,6 +739,7 @@ class CodexStructuredProviderSession {
|
|
|
712
739
|
conversationId: this.conversationId,
|
|
713
740
|
expectedTurnId: nativeTurnId,
|
|
714
741
|
text: turn.boundedText,
|
|
742
|
+
...(turn.additionalContext === undefined ? {} : { additionalContext: turn.additionalContext }),
|
|
715
743
|
clientUserMessageId: turn.attemptId
|
|
716
744
|
});
|
|
717
745
|
if (acceptance.status === "unknown") {
|
|
@@ -776,6 +804,7 @@ class CodexStructuredProviderSession {
|
|
|
776
804
|
return result === "interrupted" ? "requested" : result;
|
|
777
805
|
}
|
|
778
806
|
terminate(signal) {
|
|
807
|
+
this.#titleSync?.close();
|
|
779
808
|
terminateProcessGroup(this.child, signal);
|
|
780
809
|
}
|
|
781
810
|
}
|
|
@@ -787,6 +816,7 @@ class ClaudeStructuredProviderSession {
|
|
|
787
816
|
channel;
|
|
788
817
|
onGoal;
|
|
789
818
|
mirror;
|
|
819
|
+
contextEpoch = 0;
|
|
790
820
|
adapterId = "claude";
|
|
791
821
|
get ownedProcessId() { return this.child.pid; }
|
|
792
822
|
#activeAttemptId;
|
|
@@ -903,6 +933,8 @@ class ClaudeStructuredProviderSession {
|
|
|
903
933
|
terminateProcessGroup(this.child, signal);
|
|
904
934
|
}
|
|
905
935
|
#receive(message, onTerminal, onAccepted, onActivity) {
|
|
936
|
+
if (isNativeContextRecovery("claude", this.conversationId, message))
|
|
937
|
+
this.contextEpoch += 1;
|
|
906
938
|
const goal = claudeActiveGoal(message, this.conversationId);
|
|
907
939
|
if (goal !== undefined) {
|
|
908
940
|
const key = JSON.stringify(goal === null ? null : {
|
|
@@ -295,6 +295,36 @@ const MINOR_UPGRADES = Object.freeze([{
|
|
|
295
295
|
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
296
296
|
// Optional workspace-write permission fields; preserve all existing payloads.
|
|
297
297
|
sql: ""
|
|
298
|
+
}, {
|
|
299
|
+
fromVersion: "1.26",
|
|
300
|
+
toVersion: "1.27",
|
|
301
|
+
name: "host-upgrade-lifecycle-signals",
|
|
302
|
+
introducedIn: "next",
|
|
303
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
304
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
305
|
+
// New versioned signal sources in the existing role-runtime mailbox. No
|
|
306
|
+
// historical requests are inferred and existing records remain unchanged.
|
|
307
|
+
sql: ""
|
|
308
|
+
}, {
|
|
309
|
+
fromVersion: "1.27",
|
|
310
|
+
toVersion: "1.28",
|
|
311
|
+
name: "native-title-effect-receipts",
|
|
312
|
+
introducedIn: "next",
|
|
313
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
314
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
315
|
+
// Additive exact-identity intent/confirmation files under runtime/native-titles.
|
|
316
|
+
// Never invent native ownership or replay history during migration.
|
|
317
|
+
sql: ""
|
|
318
|
+
}, {
|
|
319
|
+
fromVersion: "1.28",
|
|
320
|
+
toVersion: "1.29",
|
|
321
|
+
name: "git-command-budgets",
|
|
322
|
+
introducedIn: "2.24.0",
|
|
323
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
324
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
325
|
+
// Optional local/remote Git budgets in config. Absence uses runtime defaults;
|
|
326
|
+
// preserve every existing payload byte rather than persisting defaults.
|
|
327
|
+
sql: ""
|
|
298
328
|
}]);
|
|
299
329
|
export function storageMinorUpgradePlan(from) {
|
|
300
330
|
if (from === CURRENT_STORAGE_VERSION)
|
|
@@ -4,7 +4,7 @@ import { validateModelConnection } from "../models/modelManagementConfig.js";
|
|
|
4
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
5
|
import { isDeepStrictEqual } from "node:util";
|
|
6
6
|
import { resolveTimeZone } from "../config/timeZone.js";
|
|
7
|
-
import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
7
|
+
import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveGitLocalTimeoutSeconds, resolveGitRemoteInactivityTimeoutSeconds, resolveGitRemoteTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
8
8
|
import { validateCapabilityGrant } from "../grant/capabilityGrant.js";
|
|
9
9
|
import { validateReleaseWorkflow } from "../release/releaseWorkflow.js";
|
|
10
10
|
import { validateReviewConfig } from "../review/reviewConfig.js";
|
|
@@ -131,6 +131,9 @@ export function validateYuiConfig(config) {
|
|
|
131
131
|
"controllerTaskConcurrency",
|
|
132
132
|
"agentLaunchInactivityTimeoutSeconds",
|
|
133
133
|
"deliveryTimeoutSeconds",
|
|
134
|
+
"gitLocalTimeoutSeconds",
|
|
135
|
+
"gitRemoteInactivityTimeoutSeconds",
|
|
136
|
+
"gitRemoteTimeoutSeconds",
|
|
134
137
|
"resourcesQuarantineTtlHours",
|
|
135
138
|
"tmuxBin",
|
|
136
139
|
"tmuxHistoryLimit",
|
|
@@ -159,6 +162,9 @@ export function validateYuiConfig(config) {
|
|
|
159
162
|
resolveControllerTaskConcurrency(config.controllerTaskConcurrency);
|
|
160
163
|
resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds);
|
|
161
164
|
resolveDeliveryTimeoutSeconds(config.deliveryTimeoutSeconds);
|
|
165
|
+
resolveGitLocalTimeoutSeconds(config.gitLocalTimeoutSeconds);
|
|
166
|
+
resolveGitRemoteInactivityTimeoutSeconds(config.gitRemoteInactivityTimeoutSeconds);
|
|
167
|
+
resolveGitRemoteTimeoutSeconds(config.gitRemoteTimeoutSeconds);
|
|
162
168
|
resolveTmuxBin(config.tmuxBin);
|
|
163
169
|
resolveTmuxHistoryLimit(config.tmuxHistoryLimit);
|
|
164
170
|
resolveTelemetryEnabled(config.telemetryEnabled);
|
|
@@ -227,7 +227,7 @@ export function createWebConversationSurface(store, surface, plan, readPage = re
|
|
|
227
227
|
if (["completed", "cancelled", "retired", "archived"].includes(task.status)) {
|
|
228
228
|
throw new WebRequestRejected("Terminal Task materials are read-only.");
|
|
229
229
|
}
|
|
230
|
-
return saveTextMaterial(store.rootDirectory(), owner.taskId, id, input);
|
|
230
|
+
return saveTextMaterial(store.rootDirectory(), owner.taskId, id, input, () => store.getConfig());
|
|
231
231
|
},
|
|
232
232
|
state(owner, offset = 0) {
|
|
233
233
|
const set = sessions(owner);
|
|
@@ -296,7 +296,7 @@ export function createWebConversationSurface(store, surface, plan, readPage = re
|
|
|
296
296
|
if (owner.scope !== "task" || input.action === "interrupt")
|
|
297
297
|
throw new WebRequestRejected("Materials require Task text input.");
|
|
298
298
|
try {
|
|
299
|
-
body = await materialInputBody(store.rootDirectory(), owner.taskId, required(body), input.materials);
|
|
299
|
+
body = await materialInputBody(store.rootDirectory(), owner.taskId, required(body), input.materials, () => store.getConfig());
|
|
300
300
|
}
|
|
301
301
|
catch (error) {
|
|
302
302
|
throw new WebRequestRejected(error instanceof Error ? error.message : "Invalid material.");
|
package/dist/web/webMaterials.js
CHANGED
|
@@ -16,24 +16,24 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
16
16
|
const TEXT_NAMES = new Set(["readme", "license", "dockerfile", "makefile"]);
|
|
17
17
|
export const MAX_MATERIAL_BYTES = 256 * 1024;
|
|
18
18
|
const PAGE_CHARACTERS = 12000;
|
|
19
|
-
async function fixedImage(home, taskId, input) {
|
|
19
|
+
async function fixedImage(home, taskId, input, getConfig) {
|
|
20
20
|
if (input.taskId !== taskId)
|
|
21
21
|
throw new WebRequestRejected("Materials must belong to the same Task.");
|
|
22
22
|
const ref = validateGitArtifactRef(input);
|
|
23
23
|
if (ref.relativePath.length > 512)
|
|
24
24
|
throw new WebRequestRejected("Image path is too long.");
|
|
25
|
-
const file = await resolveGitArtifact(home, ref);
|
|
25
|
+
const file = await resolveGitArtifact(home, ref, getConfig);
|
|
26
26
|
const metadata = rasterMetadata(file.bytes);
|
|
27
27
|
return { ...ref, digest: file.digest, kind: "image", byteSize: file.bytes.length,
|
|
28
28
|
...metadata, base64: file.bytes.toString("base64") };
|
|
29
29
|
}
|
|
30
30
|
/** One selected immutable file, never a workspace path or a remote URL.
|
|
31
31
|
* Markdown is bounded and whole so fences/tables are not split by pagination. */
|
|
32
|
-
export async function readArtifactPreview(home, taskId, ref, offset = 0) {
|
|
32
|
+
export async function readArtifactPreview(home, taskId, ref, offset = 0, getConfig) {
|
|
33
33
|
if (ref.taskId !== taskId)
|
|
34
34
|
throw new WebRequestRejected("Materials must belong to the same Task.");
|
|
35
35
|
const valid = validateGitArtifactRef(ref);
|
|
36
|
-
const file = await resolveGitArtifact(home, valid);
|
|
36
|
+
const file = await resolveGitArtifact(home, valid, getConfig);
|
|
37
37
|
return { ...valid, ...previewArtifactContent(file, offset) };
|
|
38
38
|
}
|
|
39
39
|
/** Shared data-only preview for Task and Job evidence; no path lookup or authority. */
|
|
@@ -81,12 +81,12 @@ function decodeText(bytes) {
|
|
|
81
81
|
}
|
|
82
82
|
return text;
|
|
83
83
|
}
|
|
84
|
-
async function fixedText(home, taskId, input) {
|
|
84
|
+
async function fixedText(home, taskId, input, getConfig) {
|
|
85
85
|
if (input.taskId !== taskId)
|
|
86
86
|
throw new WebRequestRejected("Materials must belong to the same Task.");
|
|
87
87
|
const ref = validateGitArtifactRef(input);
|
|
88
88
|
textPath(ref.relativePath);
|
|
89
|
-
const file = await resolveGitArtifact(home, ref);
|
|
89
|
+
const file = await resolveGitArtifact(home, ref, getConfig);
|
|
90
90
|
return { ref: { ...ref, digest: file.digest }, text: decodeText(file.bytes), bytes: file.bytes.length };
|
|
91
91
|
}
|
|
92
92
|
function page(text, offset = 0) {
|
|
@@ -100,26 +100,26 @@ function page(text, offset = 0) {
|
|
|
100
100
|
return { content: text.slice(offset, end), offset, totalCharacters: text.length,
|
|
101
101
|
nextOffset: end < text.length ? end : null };
|
|
102
102
|
}
|
|
103
|
-
export async function readTextArtifactPage(home, taskId, ref, offset = 0) {
|
|
104
|
-
const file = await fixedText(home, taskId, ref);
|
|
103
|
+
export async function readTextArtifactPage(home, taskId, ref, offset = 0, getConfig) {
|
|
104
|
+
const file = await fixedText(home, taskId, ref, getConfig);
|
|
105
105
|
return { ...file.ref, byteSize: file.bytes, ...page(file.text, offset) };
|
|
106
106
|
}
|
|
107
|
-
export async function compareTextArtifacts(home, taskId, before, after, offset = 0) {
|
|
108
|
-
const left = await fixedText(home, taskId, before);
|
|
109
|
-
const right = await fixedText(home, taskId, after);
|
|
107
|
+
export async function compareTextArtifacts(home, taskId, before, after, offset = 0, getConfig) {
|
|
108
|
+
const left = await fixedText(home, taskId, before, getConfig);
|
|
109
|
+
const right = await fixedText(home, taskId, after, getConfig);
|
|
110
110
|
// Blob identities avoid interpreting user paths as options or reading the
|
|
111
111
|
// working tree. Neither external diff drivers nor text converters can run.
|
|
112
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
112
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
113
113
|
const diff = await managedGit(repo.repoPath, [
|
|
114
114
|
"diff", "--no-ext-diff", "--no-textconv", "--no-color", "--unified=3",
|
|
115
115
|
`${left.ref.commit}:${left.ref.relativePath}`, `${right.ref.commit}:${right.ref.relativePath}`, "--"
|
|
116
|
-
], { maxBuffer: 20 * 1024 * 1024,
|
|
116
|
+
], { maxBuffer: 20 * 1024 * 1024, getConfig });
|
|
117
117
|
return { taskId, before: left.ref, after: right.ref, ...page(diff, offset) };
|
|
118
118
|
}
|
|
119
119
|
/** Saves DATA only. Sending it is a separate explicit conversation action.
|
|
120
120
|
* The normal artifact repository and commit message retain origin; no second
|
|
121
121
|
* storage/receipt schema is introduced. Unknown saves are inspected, not replayed. */
|
|
122
|
-
export async function saveTextMaterial(home, taskId, sessionId, input) {
|
|
122
|
+
export async function saveTextMaterial(home, taskId, sessionId, input, getConfig) {
|
|
123
123
|
if (!/^[A-Za-z0-9_-]{1,128}$/u.test(input.requestId)
|
|
124
124
|
|| typeof input.name !== "string" || input.name.includes("/") || input.name.includes("\\")
|
|
125
125
|
|| typeof input.content !== "string")
|
|
@@ -130,7 +130,7 @@ export async function saveTextMaterial(home, taskId, sessionId, input) {
|
|
|
130
130
|
throw new WebRequestRejected(`Material exceeds ${MAX_MATERIAL_BYTES} UTF-8 bytes or has invalid text.`);
|
|
131
131
|
}
|
|
132
132
|
decodeText(bytes);
|
|
133
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
133
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
134
134
|
await repo.ensure();
|
|
135
135
|
const head = (await repo.head());
|
|
136
136
|
if ((await repo.list(head)).some(entry => entry.relativePath === relativePath)) {
|
|
@@ -141,22 +141,22 @@ export async function saveTextMaterial(home, taskId, sessionId, input) {
|
|
|
141
141
|
return { taskId, relativePath, commit: head, digest: existing.digest };
|
|
142
142
|
}
|
|
143
143
|
return saveArtifactFile(home, taskId, { relativePath, bytes, expectedHead: head,
|
|
144
|
-
message: `Text material from Leader Session ${sessionId}; request ${input.requestId}` });
|
|
144
|
+
message: `Text material from Leader Session ${sessionId}; request ${input.requestId}` }, getConfig);
|
|
145
145
|
}
|
|
146
146
|
/** Existing durable Message body carries self-contained frozen references.
|
|
147
147
|
* Contents are deliberately absent: reading material cannot elevate it to an
|
|
148
148
|
* instruction, execute it, or broaden the receiving Agent's Task authority. */
|
|
149
|
-
export async function materialInputBody(home, taskId, body, refs) {
|
|
149
|
+
export async function materialInputBody(home, taskId, body, refs, getConfig) {
|
|
150
150
|
if (!Array.isArray(refs) || refs.length > 8)
|
|
151
151
|
throw new WebRequestRejected("Select at most eight material references.");
|
|
152
152
|
const verified = [];
|
|
153
153
|
for (const ref of refs) {
|
|
154
154
|
if (isRasterArtifact(ref.relativePath)) {
|
|
155
|
-
const image = await fixedImage(home, taskId, ref);
|
|
155
|
+
const image = await fixedImage(home, taskId, ref, getConfig);
|
|
156
156
|
verified.push({ taskId, commit: image.commit, relativePath: image.relativePath, digest: image.digest });
|
|
157
157
|
}
|
|
158
158
|
else
|
|
159
|
-
verified.push((await fixedText(home, taskId, ref)).ref);
|
|
159
|
+
verified.push((await fixedText(home, taskId, ref, getConfig)).ref);
|
|
160
160
|
}
|
|
161
161
|
if (!verified.length)
|
|
162
162
|
return body;
|
|
@@ -136,7 +136,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
136
136
|
artifacts: async (taskId, selectedCommit, offset = 0) => {
|
|
137
137
|
if (store.getTask(taskId) === null)
|
|
138
138
|
throw new WebRequestRejected("Task not found.");
|
|
139
|
-
const repo = openTaskArtifactRepository(store.rootDirectory(), taskId);
|
|
139
|
+
const repo = openTaskArtifactRepository(store.rootDirectory(), taskId, () => store.getConfig());
|
|
140
140
|
if (selectedCommit !== undefined && !repo.exists())
|
|
141
141
|
throw new WebRequestRejected("Artifact revision unavailable.");
|
|
142
142
|
const commit = selectedCommit ?? await repo.head();
|
|
@@ -151,12 +151,12 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
151
151
|
throw new WebRequestRejected("Task not found.");
|
|
152
152
|
// The browser always names a fixed revision. Never substitute HEAD after
|
|
153
153
|
// a missing object, and never execute/render artifact HTML as an app.
|
|
154
|
-
return readArtifactPreview(store.rootDirectory(), taskId, { taskId, relativePath, commit }, offset);
|
|
154
|
+
return readArtifactPreview(store.rootDirectory(), taskId, { taskId, relativePath, commit }, offset, () => store.getConfig());
|
|
155
155
|
},
|
|
156
156
|
artifactDiff: async (taskId, relativePath, commit, beforeCommit, offset = 0) => {
|
|
157
157
|
if (store.getTask(taskId) === null)
|
|
158
158
|
throw new WebRequestRejected("Task not found.");
|
|
159
|
-
return compareTextArtifacts(store.rootDirectory(), taskId, { taskId, relativePath, commit: beforeCommit }, { taskId, relativePath, commit }, offset);
|
|
159
|
+
return compareTextArtifacts(store.rootDirectory(), taskId, { taskId, relativePath, commit: beforeCommit }, { taskId, relativePath, commit }, offset, () => store.getConfig());
|
|
160
160
|
},
|
|
161
161
|
globalState: (roleName) => {
|
|
162
162
|
const role = store.getGlobalRole(roleName);
|
|
@@ -26,7 +26,7 @@ export class WorkItemChangeSetManager {
|
|
|
26
26
|
const context = requireCapturableContext(this.store, taskId, workItemId, options.taskFinalReviewContract);
|
|
27
27
|
const entries = capturableEntries(context);
|
|
28
28
|
if (context.source === "task-main") {
|
|
29
|
-
const git = new NodeGitWorkspace();
|
|
29
|
+
const git = new NodeGitWorkspace(() => this.store.getConfig());
|
|
30
30
|
for (const { entry, expectedHead } of entries) {
|
|
31
31
|
if (!await git.isClean(entry.path)) {
|
|
32
32
|
throw new Error(`Exact direct Candidate Task main must be clean: ${context.workItemId}/${entry.projectId}.`);
|
|
@@ -64,7 +64,7 @@ export class WorkItemChangeSetManager {
|
|
|
64
64
|
const workspace = this.store.getWorkItemWorkspace(item.taskId, item.id);
|
|
65
65
|
if (workspace === null)
|
|
66
66
|
return { proof: null, checks: [] };
|
|
67
|
-
const git = new NodeGitWorkspace();
|
|
67
|
+
const git = new NodeGitWorkspace(() => this.store.getConfig());
|
|
68
68
|
const projects = [];
|
|
69
69
|
const checks = [];
|
|
70
70
|
const sources = [`work-item:${taskId}/${workItemId}`,
|
|
@@ -189,7 +189,7 @@ export class WorkItemChangeSetManager {
|
|
|
189
189
|
if (unresolved !== undefined) {
|
|
190
190
|
throw new Error(`Task has an unresolved Integration Attempt: ${task.id}/${unresolved.id}.`);
|
|
191
191
|
}
|
|
192
|
-
const git = new NodeGitWorkspace();
|
|
192
|
+
const git = new NodeGitWorkspace(() => this.store.getConfig());
|
|
193
193
|
const remoteHeads = new Map();
|
|
194
194
|
const findPublishedCommit = async (entry, headCommit) => {
|
|
195
195
|
const project = this.store.getProject(entry.projectId);
|
|
@@ -2,6 +2,99 @@
|
|
|
2
2
|
|
|
3
3
|
# Session, AgentRun and notifications
|
|
4
4
|
|
|
5
|
+
## Two lifecycle layers and nonblocking checks
|
|
6
|
+
|
|
7
|
+
Controller custody of a Host and the Host's native Session lifecycle have
|
|
8
|
+
separate event/guard/transition tables. Neither is an upgrade workflow.
|
|
9
|
+
|
|
10
|
+
| Layer | Events and transitions | Authority and correlation |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| Controller → Host | Fresh physical observations recover `unavailable`/`stopped`/`starting`/`running`. Authorized launch enters `starting`; matching launch handshake enters `running`. Stop or graceful-exit request enters `stopping`; sending exit does not prove `stopped`. Physical absence confirms `stopped`; unknown effects enter `unavailable`. | Owner, local operation/revision, returned binding and native Session ID. Durable Role/Task intent, launch reservations and exact process custody remain authoritative. |
|
|
13
|
+
| Host → native Session | Launch, attachment, input acceptance, native activity, terminal, persistence, disconnect and exit drive the native states described below. | Native Session, proxy process, attempt and Turn identity; existing writer/resource fences still authorize effects. |
|
|
14
|
+
|
|
15
|
+
The Controller wraps the shared Host port for launch/restore, observation and
|
|
16
|
+
stop, including graceful-upgrade exit. A competing physical operation is
|
|
17
|
+
rejected before its side effect, not queued or replayed by this FSM. Late
|
|
18
|
+
reads and old bindings cannot overwrite newer custody; identical observations
|
|
19
|
+
produce no hook feedback loop. A Controller restart reconstructs by observation,
|
|
20
|
+
never by replaying the process-local state. Native completion/attachment exit
|
|
21
|
+
does not prove Host process exit. Legal same-conversation resume correlates the
|
|
22
|
+
layers without sharing their state enums or inventing another durable authority.
|
|
23
|
+
|
|
24
|
+
Host-side native Session lifecycle state is owned by an explicit transition table, not
|
|
25
|
+
by version detection. Launch/submit enters `starting`; attachment facts select
|
|
26
|
+
`idle`, `busy`, `ready` (owned input), or `delivery-unknown`; acceptance enters
|
|
27
|
+
`ready`; native execution enters `busy`; terminal handling enters `settling`;
|
|
28
|
+
persisted settlement returns to observed `idle`/`busy`. Disconnect enters
|
|
29
|
+
`starting`, classified failures retain their failure outcome, and exit enters
|
|
30
|
+
`exited`. Only an explicit launch can leave `exited`. Native-TUI attachment
|
|
31
|
+
readiness has its own event: `ready` there is not proof of native quiescence.
|
|
32
|
+
Illegal edges do not mutate state; stale process, attempt and terminal-Turn
|
|
33
|
+
facts are ignored. Exact writer/resource guards remain at command admission.
|
|
34
|
+
This process-local reducer reconstructs from existing launch and native facts;
|
|
35
|
+
it adds no persisted Session authority or upgrade workflow state machine.
|
|
36
|
+
|
|
37
|
+
Optional before/after observations are immutable, deferred and independently
|
|
38
|
+
coalesced (at most one running and one latest pending observation per check).
|
|
39
|
+
“Before” describes the transition's source, not an awaited precondition.
|
|
40
|
+
A slow or failing check cannot veto a transition or hold event folding/input
|
|
41
|
+
delivery; checks must yield for I/O and honour cancellation before effects.
|
|
42
|
+
They may recommend work to the separate fenced upgrade coordinator.
|
|
43
|
+
Controller Host-state hooks suggest version reconciliation. Host Session-state
|
|
44
|
+
hooks asynchronously observe current Skill metadata on attachment/settlement,
|
|
45
|
+
without reading Skill bodies, committing a notice or assuming model memory.
|
|
46
|
+
The exact per-input integrity check remains an ingress safeguard: invalid
|
|
47
|
+
Skills can reject that input, but an optional check cannot stall lifecycle.
|
|
48
|
+
Only accepted input advances the existing notice baseline; unchanged input
|
|
49
|
+
is passed through without any extra content.
|
|
50
|
+
|
|
51
|
+
Controller startup, Host connection/exit observations, Host state-change
|
|
52
|
+
signals and event persistence trigger version checks without an explicit
|
|
53
|
+
`update` request. Existing periodic reconciliation covers missed hints,
|
|
54
|
+
including providers without reliable background-execution-end events and
|
|
55
|
+
graceful exits that cannot emit a post-exit hint. Checks
|
|
56
|
+
compare actual loaded Host/Endpoint implementations with this Controller's
|
|
57
|
+
loaded target, never npm `latest`. Incomplete readiness on matching versions
|
|
58
|
+
is not an upgrade reason. Checks stop on shutdown; already-admitted work
|
|
59
|
+
retains the existing durable mailbox. A request pinned to a different target
|
|
60
|
+
stays waiting for explicit recovery, not silent retargeting. An explicitly
|
|
61
|
+
stopped Session is never restarted by a check.
|
|
62
|
+
|
|
63
|
+
## Host upgrade and context checks
|
|
64
|
+
|
|
65
|
+
`update` reports package installation, verified Controller startup and each
|
|
66
|
+
Host separately. `update --hosts-status` reads current per-Host evidence without
|
|
67
|
+
installing, starting or advancing a switch. Supported controlled Codex Hosts fence new input immediately,
|
|
68
|
+
wait for the current native Turn and event persistence, inspect native background
|
|
69
|
+
execution, and release their owned Endpoint before exiting gracefully. The
|
|
70
|
+
existing role-runtime mailbox retains the exact generation, native Session,
|
|
71
|
+
configuration digest and drain receipt across Controller restarts. A successor
|
|
72
|
+
uses the recorded effective configuration and resumes that same conversation;
|
|
73
|
+
only its loaded Host/Endpoint and control/event handshake confirm success.
|
|
74
|
+
Queued Messages remain original and accepted/unknown inputs are never replayed.
|
|
75
|
+
|
|
76
|
+
Paused Roles stay paused. Older Hosts, native-TUI-only attachments and Endpoints
|
|
77
|
+
without a quiescence capability are retained with an unsupported diagnosis; no
|
|
78
|
+
unsupported drain command is sent. A busy/unknown native execution has no
|
|
79
|
+
bounded upgrade-completion promise. An uncertain launch is not automatically
|
|
80
|
+
repeated; a later exact successful handshake can settle the retained request.
|
|
81
|
+
This fence covers Host-controlled ingress, not arbitrary external native clients;
|
|
82
|
+
it never terminates a shared daemon or native conversation.
|
|
83
|
+
|
|
84
|
+
Host Skill checks still run before every input. Unchanged inputs are passed
|
|
85
|
+
through exactly, with no notice, marker, separator or extra native input part.
|
|
86
|
+
The attachment's loading guidance explains this managed-input contract; no
|
|
87
|
+
notice is not a reason to repeat the check. Unmediated clients or uncertain
|
|
88
|
+
current input routing retain the explicit Manifest check fallback; reusing a
|
|
89
|
+
conversation alone does not establish Host mediation. Cold attachment,
|
|
90
|
+
affected Skill changes and observed native compaction supply loading guidance;
|
|
91
|
+
compaction invalidates the next input's metadata baseline. Without a reliable
|
|
92
|
+
native event, the Agent must still recover any missing instructions using the
|
|
93
|
+
Manifest. Neither a version nor an accepted notice proves model memory.
|
|
94
|
+
Routine checks and empty inboxes are not user-facing progress.
|
|
95
|
+
|
|
96
|
+
## Session stop and runtime observations
|
|
97
|
+
|
|
5
98
|
Global Session stop/replacement uses the same exact native-execution quiescence
|
|
6
99
|
boundary as Task Sessions. A departed Host or archived Session alone is not
|
|
7
100
|
proof that an accepted input ended: its retained Provider binding is inspected
|
|
@@ -31,7 +124,9 @@ Samples use a process-monotonic clock and hashed target/request labels, with
|
|
|
31
124
|
at most 16 recent and 16 active samples plus the slowest completed sample.
|
|
32
125
|
In-flight request count is not queue depth; total client-to-route wait is not
|
|
33
126
|
measured by the server alone. These observations neither identify an old incident
|
|
34
|
-
retroactively nor establish quiescence
|
|
127
|
+
retroactively nor establish quiescence. A Controller restart does not itself
|
|
128
|
+
prove Host replacement; asynchronous lifecycle checks still require a safe
|
|
129
|
+
drain and the successor's actual handshake.
|
|
35
130
|
|
|
36
131
|
`observations.commands.routes.transport` correlates socket accept-to-route,
|
|
37
132
|
complete-frame receive-to-route and route-to-local-flush stages by the same
|
|
@@ -2,6 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
# Session、AgentRun 与通知
|
|
4
4
|
|
|
5
|
+
## 两层生命周期与非阻塞检查
|
|
6
|
+
|
|
7
|
+
Controller 对 Host 的管理状态、Host 对原生 Session 的生命周期分别拥有自己的
|
|
8
|
+
事件、guard 和转换表,都不是升级流程状态机。
|
|
9
|
+
|
|
10
|
+
| 层级 | 事件与转换 | 权限及关联 |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| Controller → Host | 新鲜物理观测恢复 `unavailable`/`stopped`/`starting`/`running`;合法 launch 进入 `starting`,匹配启动握手进入 `running`。停止或优雅退出请求进入 `stopping`,发送退出不证明 `stopped`;物理不存在才确认 `stopped`,未知效果进入 `unavailable`。 | owner、本地操作/观测 revision、返回 binding 和原生 Session ID;持久 Role/Task 意图、启动 reservation 和精确进程权属仍是权威。 |
|
|
13
|
+
| Host → 原生 Session | launch、附件、输入接受、原生活动、终态、持久化、断连和退出驱动下述原生状态。 | 原生 Session、proxy 进程、attempt 和 Turn 身份;原有 writer/资源栅栏继续授权副作用。 |
|
|
14
|
+
|
|
15
|
+
Controller 在共享 Host port 处统一管理启动/恢复、观测与停止(含安全升级的优雅退出)。
|
|
16
|
+
冲突物理操作在副作用前拒绝,不由状态机另建队列或重放。迟到读取和旧 binding 不能覆盖
|
|
17
|
+
新管理状态;相同观测不重复触发检查反馈循环。Controller 重启后从观测重建,
|
|
18
|
+
不重放进程内状态。原生轮次完成或附件退出不能证明 Host 进程已退出;
|
|
19
|
+
合法恢复通过同一原生对话关联两层,不共用状态枚举或新增持久权威。
|
|
20
|
+
|
|
21
|
+
Host 内的原生 Session 生命周期由明确的转换表管理,不由版本检查管理。
|
|
22
|
+
launch/submit 进入 `starting`;附件事实选择 `idle`、`busy`、
|
|
23
|
+
`ready`(持有输入)或 `delivery-unknown`;接受输入进入 `ready`;
|
|
24
|
+
原生执行进入 `busy`;终态处理进入 `settling`;持久化结清后回到观测到的
|
|
25
|
+
`idle`/`busy`。断连进入 `starting`,分类失败保留对应失败状态,退出进入
|
|
26
|
+
`exited`,只有显式 launch 可离开它。原生 TUI 附件就绪有独立事件,
|
|
27
|
+
其 `ready` 不证明原生执行静止。非法边不改变状态;过期的进程、attempt、
|
|
28
|
+
终态 Turn 事实被忽略。精确 writer/资源 guard 仍在命令准入处执行。
|
|
29
|
+
状态机仅在进程内从现有 launch 和原生事实恢复,不新增持久 Session 权威或升级流程状态机。
|
|
30
|
+
|
|
31
|
+
可组合的前/后检查接收不可变观测,各自异步执行并合并重复提示
|
|
32
|
+
(每项最多一个执行中和一个最新待处理观测)。“前”表示转换来源,不是等待完成的前置条件。
|
|
33
|
+
慢检查或失败不能否决转换、阻塞事件折叠或输入投递;检查回调必须异步等待 I/O,
|
|
34
|
+
并在请求副作用前响应取消。它们只能向独立的、受精确栅栏保护的升级协调器提出请求。
|
|
35
|
+
Controller 的 Host 状态 hook 提议版本对账;Host 的 Session 状态 hook 在附件和结清时
|
|
36
|
+
异步观测当前 Skill 元数据,不读正文、不提交通知基线、不假定模型记忆。
|
|
37
|
+
逐输入完整性核验仍是投递安全门:无效 Skill 可以拒绝该次输入,但可选检查不能卡住生命周期。
|
|
38
|
+
仍只有被接受的输入推进通知基线,未变化输入原样通过,不额外注入内容。
|
|
39
|
+
|
|
40
|
+
Controller 启动、Host 连接/退出观测、Host 状态变化 signal 和事件持久化均可触发版本检查,
|
|
41
|
+
不依赖显式 `update`。现有周期对账覆盖丢失提示、无法可靠提供后台执行结束事件的提供方,
|
|
42
|
+
以及无法在退出后发出提示的优雅退出。
|
|
43
|
+
比较对象是实际加载的 Host/Endpoint 实现与当前 Controller 已加载的目标,不查询 npm `latest`。
|
|
44
|
+
版本匹配但就绪证据未齐不是升级理由。关闭时停止检查,已准入工作仍保留现有持久 mailbox;
|
|
45
|
+
请求固定的目标与当前 Controller 不同时保持等待,交由显式恢复,不静默改目标。
|
|
46
|
+
检查不会复活明确停止的 Session。
|
|
47
|
+
|
|
48
|
+
## Host 升级与上下文检查
|
|
49
|
+
|
|
50
|
+
`update` 分别报告程序安装、Controller 启动核验及逐 Host 的状态。
|
|
51
|
+
`update --hosts-status` 只读查询逐 Host 证据,不安装、启动或推进切换。支持此能力的
|
|
52
|
+
受控 Codex Host 先关闭新输入准入,等待当前原生轮次和事件持久化、核验后台执行,
|
|
53
|
+
释放自己持有的 Endpoint 后优雅退出。现有 role-runtime mailbox 保存精确实现代际、
|
|
54
|
+
native Session、配置摘要和排空回执,Controller 重启后仍可继续。
|
|
55
|
+
后继使用原 effective 配置接回同一对话;实际加载的 Host/Endpoint、控制与事件能力
|
|
56
|
+
握手通过后才确认成功。队列中的 Message 原文不变,不重放 accepted 或 unknown 输入。
|
|
57
|
+
|
|
58
|
+
暂停角色保持暂停。旧 Host、仅有原生 TUI 的附件及不能证明静止的 Endpoint
|
|
59
|
+
保留并报告不支持,不反复发送不支持的排空命令。忙碌或未知执行不承诺限时升级。
|
|
60
|
+
启动结果未知不自动重放;后续精确成功握手可结清原请求。
|
|
61
|
+
准入栅栏覆盖 Host 控制的入口,不控制任意外部原生客户端,也不会终止共享 daemon 或原生对话。
|
|
62
|
+
|
|
63
|
+
Host 仍逐输入机械检查 Skills。未变化时原文原样通过,不添加检查通知、隐藏标记、
|
|
64
|
+
分隔换行或原生输入片段。附件加载指引说明这一托管输入约定,不因没有通知而重复检查。
|
|
65
|
+
绕过 Host 的客户端或当前输入路由不明时仍通过 Manifest 显式检查;复用同一对话
|
|
66
|
+
本身不证明当前输入受 Host 托管。首次附件、局部 Skill 变化、已观测到的原生压缩
|
|
67
|
+
提供相应加载指引;压缩使下一次输入的元数据基线失效。
|
|
68
|
+
提供方无法可靠暴露事件时,Agent 仍须通过 Manifest 恢复缺失指令。
|
|
69
|
+
版本和已接受的通知都不能证明模型记忆;例行检查、角色版本和空 inbox 不作为用户进度播报。
|
|
70
|
+
|
|
71
|
+
## 会话停止与运行时观测
|
|
72
|
+
|
|
5
73
|
局部停止等待自身的持久清理义务完成,不等待整个 Home 的 `scheduler.scan`
|
|
6
74
|
循环排空。signal 只请求调度;完成仍以现有物理停止证明及清理事务为准。
|
|
7
75
|
超时保留义务并阻止替换;选择失败和新会话启动未确认分别报告,不允许重放未知效果。
|
|
@@ -16,7 +84,8 @@ interrupt-owned 输入仍保留原处理路径。
|
|
|
16
84
|
目标/请求标签仅保存哈希;每组最多保留 16 个近期样本、16 个活动样本及最慢完成样本。
|
|
17
85
|
in-flight 请求数不是排队数,服务端不能独自测量客户端到路由的完整等待。
|
|
18
86
|
观测不能反推旧事故的唯一根因,
|
|
19
|
-
|
|
87
|
+
也不构成静止证明;Controller 重启本身不证明 Host 已替换,
|
|
88
|
+
异步生命周期检查仍须经过安全排空和后继实际握手。
|
|
20
89
|
|
|
21
90
|
`observations.commands.routes.transport` 使用与 route service time 相同的请求哈希,
|
|
22
91
|
分别关联 socket accept→route、完整帧 receive→route、route→本地 flush。
|
package/docs/project-refresh.md
CHANGED
|
@@ -80,7 +80,8 @@ upstream is left untouched and carries no such promise.
|
|
|
80
80
|
|
|
81
81
|
## Scope of adoption
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
Refresh bookkeeping adds no persistent Yui schema. The shared remote Git
|
|
84
|
+
budget settings require storage 1.29; see [configuration](roles-and-configuration.md#git-operation-budgets).
|
|
85
|
+
Installing this code does not itself refresh a real Project. A normal authorized
|
|
85
86
|
refresh applies the behavior; Task completion, integration, publication and
|
|
86
87
|
archive remain separate operations.
|
|
@@ -63,5 +63,6 @@ upstream 保持原样,不作这一承诺。
|
|
|
63
63
|
|
|
64
64
|
## 采用边界
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
refresh 记账不新增 Yui 持久 schema;共享远程 Git 预算配置要求存储 1.29,
|
|
67
|
+
见[配置说明](roles-and-configuration.zh-CN.md#git-操作预算)。安装实现代码不等于刷新真实 Project;
|
|
67
68
|
正常获授权的 refresh 才应用此行为。Task 完成、集成、发布和归档仍是独立操作。
|