@zq-silk/yui 0.15.8 → 0.15.11
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 +2 -0
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +211 -14
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/cli/commandCatalog.js +40 -16
- package/dist/cli/interactionPolicy.js +3 -3
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +403 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/operatorCommands.js +33 -2
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActivationCommands.js +22 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +846 -155
- package/dist/commands/taskIntegrationCommands.js +16 -38
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +28 -16
- package/dist/context/taskContext.js +64 -5
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +11 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
- package/dist/controller/globalInputDelivery.js +119 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +81 -6
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +312 -382
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/kernel/builtinCapabilities.js +32 -24
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +423 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/plugins/pluginService.js +11 -3
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +72 -15
- package/dist/repository/taskWorkspaceCoordinator.js +134 -0
- package/dist/repository/taskWorkspacePreparer.js +120 -49
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/resources/projectResource.js +0 -48
- package/dist/resources/projectResourceService.js +3 -81
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentHost.js +152 -72
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +12 -1
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +29 -16
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +173 -7
- package/dist/storage/sqliteStore.js +41 -22
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -1
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/taskActivation.js +26 -0
- package/dist/task/taskActivationService.js +85 -69
- package/dist/task/taskSubmission.js +236 -0
- package/dist/web/assets/client/app.js +58 -2
- package/dist/web/assets/client/components.js +1 -0
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/assets/client/taskSurface.js +202 -7
- package/dist/web/assets/client/view.js +7 -4
- package/dist/web/assets/shell.js +23 -0
- package/dist/web/assets/styles/layout.js +1 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +135 -4
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +225 -8
- package/dist/workItem/workItem.js +14 -10
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/agent-result-consumption.md +2 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +2 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +44 -32
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -79
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +2 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +2 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +320 -217
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +2 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +41 -0
- package/docs/release-workflow.zh-CN.md +266 -0
- package/docs/roles-and-configuration.md +2 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +225 -1
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -57
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +2 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +2 -0
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +26 -0
- package/docs/testing/verification-levels.zh-CN.md +80 -0
- package/i18n/README.zh-CN.md +199 -10
- package/package.json +2 -1
- package/skills/yui-leader/SKILL.md +88 -331
- package/skills/yui-leader/references/execution.md +405 -0
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/task-plugins.md +8 -4
- package/skills/yui-operator/SKILL.md +22 -4
- package/skills/yui-runtime/SKILL.md +27 -0
- package/skills/yui-runtime/references/publication.md +20 -0
|
@@ -65,7 +65,9 @@ export class FileTaskRuntimeIsolation {
|
|
|
65
65
|
this.#resourceRegistrar().registerTaskRuntimeIsolation(descriptor);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
ensureDirectoryChain(this.#
|
|
68
|
+
ensureDirectoryChain(this.#controlPlane.managedRuntimeRoot === undefined
|
|
69
|
+
? this.#runtimeRoot
|
|
70
|
+
: this.#controlPlane.yuiHome, dirname(root));
|
|
69
71
|
mkdirSync(root, { mode: 0o700 });
|
|
70
72
|
try {
|
|
71
73
|
const marker = {
|
|
@@ -255,10 +257,22 @@ export function assertTaskRuntimeIsolationPreflight(input) {
|
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
259
|
const control = normalizeControlBoundary(input.controlPlane);
|
|
258
|
-
const protectedPaths = [control.yuiHome, ...control.globalInstallPaths];
|
|
259
260
|
for (const root of Object.values(descriptor.roots)) {
|
|
260
|
-
if (
|
|
261
|
-
throw new Error("Task runtime roots overlap
|
|
261
|
+
if (control.globalInstallPaths.some((path) => pathsOverlap(root, path))) {
|
|
262
|
+
throw new Error("Task runtime roots overlap a global install path.");
|
|
263
|
+
}
|
|
264
|
+
// Precise partition isolation: a runtime root may occupy Home only when it
|
|
265
|
+
// is fully within the single designated managed runtime partition. Every
|
|
266
|
+
// other part of Home — the database, managed Git workspaces, projects —
|
|
267
|
+
// stays protected. Both Task and integration runtimes now supply such a
|
|
268
|
+
// partition; a caller that supplies none keeps the whole-Home overlap ban.
|
|
269
|
+
if (pathsOverlap(root, control.yuiHome)) {
|
|
270
|
+
const partition = control.managedRuntimeRoot;
|
|
271
|
+
const withinPartition = partition !== undefined
|
|
272
|
+
&& (root === partition || isWithin(partition, root));
|
|
273
|
+
if (!withinPartition) {
|
|
274
|
+
throw new Error("Task runtime roots overlap the control YUI_HOME outside the managed runtime partition.");
|
|
275
|
+
}
|
|
262
276
|
}
|
|
263
277
|
if (pathsOverlap(root, descriptor.workspace.root)) {
|
|
264
278
|
throw new Error("Task runtime roots must not dirty the managed workspace.");
|
|
@@ -441,11 +455,21 @@ function taskRuntimeServiceNamespace(taskId, owner, runtimeRoot) {
|
|
|
441
455
|
])).slice(0, 24)}`;
|
|
442
456
|
}
|
|
443
457
|
function normalizeControlBoundary(value) {
|
|
458
|
+
const yuiHome = canonicalPath(value.yuiHome, "Control YUI_HOME");
|
|
459
|
+
const managedRuntimeRoot = value.managedRuntimeRoot === undefined
|
|
460
|
+
? undefined
|
|
461
|
+
: canonicalPath(value.managedRuntimeRoot, "Managed runtime root");
|
|
462
|
+
if (managedRuntimeRoot !== undefined
|
|
463
|
+
&& managedRuntimeRoot !== yuiHome
|
|
464
|
+
&& !isWithin(yuiHome, managedRuntimeRoot)) {
|
|
465
|
+
throw new Error("Managed runtime root must be within the control YUI_HOME.");
|
|
466
|
+
}
|
|
444
467
|
return Object.freeze({
|
|
445
|
-
yuiHome
|
|
468
|
+
yuiHome,
|
|
446
469
|
controllerSocketPath: canonicalPath(value.controllerSocketPath, "Controller socket path"),
|
|
447
470
|
tmuxNamespace: requireIdentity(value.tmuxNamespace, "Controller tmux namespace"),
|
|
448
|
-
globalInstallPaths: Object.freeze((value.globalInstallPaths ?? []).map((path) => canonicalPath(path, "Global install path")))
|
|
471
|
+
globalInstallPaths: Object.freeze((value.globalInstallPaths ?? []).map((path) => canonicalPath(path, "Global install path"))),
|
|
472
|
+
...(managedRuntimeRoot === undefined ? {} : { managedRuntimeRoot })
|
|
449
473
|
});
|
|
450
474
|
}
|
|
451
475
|
function validateObservation(resource) {
|
|
@@ -209,7 +209,8 @@ export class TmuxSessionHost {
|
|
|
209
209
|
throw toRuntimeLaunchFailure(error, "validation", launchContext);
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
-
const interactiveCodex = request.owner.scope === "global" && request.adapterId === "codex"
|
|
212
|
+
const interactiveCodex = request.owner.scope === "global" && request.adapterId === "codex"
|
|
213
|
+
&& planned.launch.providerControl === undefined;
|
|
213
214
|
let reuseInteractivePane = false;
|
|
214
215
|
if (interactiveCodex) {
|
|
215
216
|
let status;
|
|
@@ -310,6 +311,7 @@ export class TmuxSessionHost {
|
|
|
310
311
|
}
|
|
311
312
|
const reservation = broker.reserve(Object.freeze({
|
|
312
313
|
schemaVersion: 2,
|
|
314
|
+
...(request.runId === undefined ? {} : { startupRunId: request.runId }),
|
|
313
315
|
command: planned.launch.command,
|
|
314
316
|
args: [...planned.launch.args],
|
|
315
317
|
environment: { ...planned.launch.env },
|
|
@@ -577,8 +579,8 @@ export class AgentHostPromptPushAdapter {
|
|
|
577
579
|
try {
|
|
578
580
|
const result = await sendAgentHostSteerControl({
|
|
579
581
|
home: this.home,
|
|
580
|
-
scope:
|
|
581
|
-
taskId: request.owner.taskId,
|
|
582
|
+
scope: request.owner.scope,
|
|
583
|
+
...(request.owner.scope === "task" ? { taskId: request.owner.taskId } : {}),
|
|
582
584
|
roleName: request.owner.roleName,
|
|
583
585
|
control: {
|
|
584
586
|
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
@@ -4,6 +4,8 @@ import { queueLeaderWakeup } from "./wakeupQueue.js";
|
|
|
4
4
|
export const LEADER_ATTENTION_REQUIRED_EVENT = "leader.attention-required";
|
|
5
5
|
/** The sole Task-event boundary into the global Operator mailbox. */
|
|
6
6
|
export function enqueueOperatorEvent(store, event, reason, now) {
|
|
7
|
+
if (store.getTask?.(event.taskId)?.status === "archived")
|
|
8
|
+
return;
|
|
7
9
|
enqueueWork(store, { kind: "operator" }, reason, now, [
|
|
8
10
|
{ type: "event", taskId: event.taskId, id: event.id }
|
|
9
11
|
], {
|
|
@@ -13,6 +15,8 @@ export function enqueueOperatorEvent(store, event, reason, now) {
|
|
|
13
15
|
}
|
|
14
16
|
/** Routes one Role fact to its supervisor without exposing Provider details to Operator code. */
|
|
15
17
|
export function routeRoleEvent(store, event, roleName, reason, now) {
|
|
18
|
+
if (store.getTask?.(event.taskId)?.status === "archived")
|
|
19
|
+
return;
|
|
16
20
|
if (roleName === "leader") {
|
|
17
21
|
enqueueOperatorEvent(store, event, reason, now);
|
|
18
22
|
return;
|
|
@@ -210,9 +210,11 @@ export function projectTaskExecution(facts) {
|
|
|
210
210
|
&& runs.filter((run) => run.workItemId === item.id).at(-1)?.status === "failed");
|
|
211
211
|
const candidateReady = workItems.some((item) => (item.status === "open" && item.currentCandidateId !== undefined));
|
|
212
212
|
const blockedIntegration = integrations.some((attempt) => attempt.status === "blocked");
|
|
213
|
+
const conflictedIntegration = integrations.find((attempt) => attempt.status === "conflicted");
|
|
213
214
|
const unresolvedIntegration = integrations.some((attempt) => (attempt.status === "running"
|
|
214
215
|
|| attempt.status === "validating"
|
|
215
|
-
|| attempt.status === "blocked"
|
|
216
|
+
|| attempt.status === "blocked"
|
|
217
|
+
|| attempt.status === "conflicted"));
|
|
216
218
|
const hasLeaderMismatch = attention.some((item) => item.kind === "identity-mismatch");
|
|
217
219
|
const renderAttention = () => {
|
|
218
220
|
const first = attention[0];
|
|
@@ -279,6 +281,15 @@ export function projectTaskExecution(facts) {
|
|
|
279
281
|
if (attention.length > 0) {
|
|
280
282
|
return renderAttention();
|
|
281
283
|
}
|
|
284
|
+
if (conflictedIntegration !== undefined) {
|
|
285
|
+
return render({
|
|
286
|
+
task, status: "needs-leader-action", owner: "leader",
|
|
287
|
+
action: "resolve-integration-conflicts",
|
|
288
|
+
summary: `Integration ${conflictedIntegration.id} has Git conflicts; resolve its workspace and continue.`,
|
|
289
|
+
reason: "integration-conflicted", monitoring, failClosed: false,
|
|
290
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
291
|
+
});
|
|
292
|
+
}
|
|
282
293
|
if (blockedIntegration || failedWork || hasLeaderMismatch) {
|
|
283
294
|
return render({
|
|
284
295
|
task,
|
|
@@ -37,7 +37,13 @@ const IMMEDIATE_KINDS = new Set([
|
|
|
37
37
|
"activation-failed",
|
|
38
38
|
"task-reopened",
|
|
39
39
|
"leader-turn-failed",
|
|
40
|
-
"role-turn-failed"
|
|
40
|
+
"role-turn-failed",
|
|
41
|
+
// A claimed interrupt-then handoff for a no-Run Leader turn: the operator/Leader
|
|
42
|
+
// explicitly redirected, so the release wake bypasses the aggregation debounce.
|
|
43
|
+
// It is still held by the leader-mailbox busy-gate until the interrupted native
|
|
44
|
+
// Turn reaches its proven terminal (decision-3 §4), so "immediate" only removes
|
|
45
|
+
// the extra 60s aggregation delay once that terminal actually opens the gate.
|
|
46
|
+
"interrupt-then"
|
|
41
47
|
]);
|
|
42
48
|
export function wakeReason(kind, ref) {
|
|
43
49
|
if (ref === undefined)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mergePendingWakeup } from "./pendingWakeup.js";
|
|
2
2
|
export function queueLeaderWakeup(store, taskId, reason, now) {
|
|
3
|
+
if (store.getTask?.(taskId)?.status === "archived")
|
|
4
|
+
return null;
|
|
3
5
|
if (store.enqueueLeaderWakeup !== undefined) {
|
|
4
6
|
return store.enqueueLeaderWakeup(taskId, reason, now);
|
|
5
7
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
2
|
-
import { delimiter,
|
|
2
|
+
import { delimiter, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
4
|
import { configuredAgentToDefinition, createConfiguredAgent } from "../agent/agent.js";
|
|
5
5
|
import { assertRoleRuntimeMutationAllowed } from "../commands/roleRuntimeGuard.js";
|
|
6
6
|
import { resolveTmuxBin } from "../config/yuiConfig.js";
|
|
7
7
|
import { usageError } from "../errors/cliError.js";
|
|
8
|
+
import { managedGlobalRoleWorkspace } from "../storage/homeLayout.js";
|
|
8
9
|
import { createGlobalRole, createRoleAgentBinding, updateGlobalRole } from "../role/role.js";
|
|
9
10
|
import { SYSTEM_LEADER_ROLE, SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
10
11
|
import { initializeCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
@@ -48,20 +49,28 @@ export async function runSetupCommand(args, env, executor, io = {}) {
|
|
|
48
49
|
const currentOperator = store.getGlobalRole(SYSTEM_OPERATOR_ROLE);
|
|
49
50
|
const selected = await selectOperatorAgent(choices, currentOperator, store.getConfig().defaultAgent, question, io);
|
|
50
51
|
const agent = selected.existing ?? createConfiguredAgent(selected.id, selected.adapterId, selected.command, [], [], new Date());
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
// The built-in Global Operator/Leader run under a Home-internal working
|
|
53
|
+
// directory. Yui never auto-invents an external scratch root here: a user
|
|
54
|
+
// who wants an external project-input root configures `default-workspace`
|
|
55
|
+
// explicitly (kept outside Home by its own guard). An already-configured one
|
|
56
|
+
// is carried forward and normalized; none is fabricated.
|
|
57
|
+
const configuredWorkspace = store.getConfig().defaultWorkspace;
|
|
58
|
+
const externalWorkspace = configuredWorkspace === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: resolveWorkspace(configuredWorkspace, home);
|
|
61
|
+
const roleWorkspace = managedGlobalRoleWorkspace(home);
|
|
62
|
+
mkdirSync(roleWorkspace, { recursive: true, mode: 0o700 });
|
|
63
|
+
const roleStatus = saveMinimumConfiguration(store, agent, roleWorkspace, externalWorkspace, new Set(choices.map(({ id }) => id)));
|
|
53
64
|
return `${[
|
|
54
65
|
"Yui setup complete.",
|
|
55
66
|
`Yui home: ${home}.`,
|
|
56
|
-
`Operator
|
|
57
|
-
`Leader
|
|
58
|
-
`
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"Provider login and native key/initialization confirmations remain with the Provider; setup did not call a model.",
|
|
64
|
-
"Run `yui operator enter` to continue, or `yui config show` to inspect configuration."
|
|
67
|
+
`Operator: ${agent.id} (${roleStatus.operator}).`,
|
|
68
|
+
`Leader: ${roleStatus.leaderAgentId} (${roleStatus.leader}).`,
|
|
69
|
+
`Global Role workspace: ${roleWorkspace}.`,
|
|
70
|
+
...(externalWorkspace === undefined
|
|
71
|
+
? []
|
|
72
|
+
: [`Default workspace: ${externalWorkspace}.`]),
|
|
73
|
+
"Run `yui config show` to inspect configuration, or `yui operator enter` to start an Operator session for configuration changes or development tasks."
|
|
65
74
|
].join("\n")}\n`;
|
|
66
75
|
}
|
|
67
76
|
finally {
|
|
@@ -121,14 +130,18 @@ async function selectOperatorAgent(choices, currentOperator, defaultAgent, quest
|
|
|
121
130
|
}
|
|
122
131
|
return selected;
|
|
123
132
|
}
|
|
124
|
-
function saveMinimumConfiguration(store, agent,
|
|
133
|
+
function saveMinimumConfiguration(store, agent, roleWorkspace, externalWorkspace, usableAgentIds) {
|
|
125
134
|
return store.transaction((tx) => {
|
|
126
135
|
tx.saveConfiguredAgent(agent);
|
|
127
136
|
const config = tx.getConfig();
|
|
128
137
|
tx.saveConfig({
|
|
129
138
|
...config,
|
|
130
139
|
defaultAgent: config.defaultAgent ?? agent.id,
|
|
131
|
-
|
|
140
|
+
// Only persist a default workspace the user actually configured; setup no
|
|
141
|
+
// longer fabricates an external Home-sibling root.
|
|
142
|
+
...(externalWorkspace === undefined
|
|
143
|
+
? {}
|
|
144
|
+
: { defaultWorkspace: config.defaultWorkspace ?? externalWorkspace })
|
|
132
145
|
});
|
|
133
146
|
const now = new Date();
|
|
134
147
|
const leader = tx.getGlobalRole(SYSTEM_LEADER_ROLE);
|
|
@@ -140,7 +153,7 @@ function saveMinimumConfiguration(store, agent, workspace, usableAgentIds) {
|
|
|
140
153
|
roleName: SYSTEM_LEADER_ROLE
|
|
141
154
|
}, "creation");
|
|
142
155
|
const definition = configuredAgentToDefinition(agent);
|
|
143
|
-
tx.saveGlobalRole(createGlobalRole(SYSTEM_LEADER_ROLE, [createRoleAgentBinding(definition)], agent.id,
|
|
156
|
+
tx.saveGlobalRole(createGlobalRole(SYSTEM_LEADER_ROLE, [createRoleAgentBinding(definition)], agent.id, roleWorkspace, now));
|
|
144
157
|
leaderStatus = "created";
|
|
145
158
|
leaderAgentId = agent.id;
|
|
146
159
|
}
|
|
@@ -166,7 +179,7 @@ function saveMinimumConfiguration(store, agent, workspace, usableAgentIds) {
|
|
|
166
179
|
scope: "global",
|
|
167
180
|
roleName: SYSTEM_OPERATOR_ROLE
|
|
168
181
|
}, "creation");
|
|
169
|
-
tx.saveGlobalRole(createGlobalRole(SYSTEM_OPERATOR_ROLE, [binding], agent.id,
|
|
182
|
+
tx.saveGlobalRole(createGlobalRole(SYSTEM_OPERATOR_ROLE, [binding], agent.id, roleWorkspace, now));
|
|
170
183
|
return {
|
|
171
184
|
operator: "created",
|
|
172
185
|
leader: leaderStatus,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* The single authority for every Yui self-managed path derived from a Home.
|
|
4
|
+
*
|
|
5
|
+
* Historically the managed Git worktrees and provider runtimes escaped Home
|
|
6
|
+
* through two different mechanisms: worktree/task roots were derived from the
|
|
7
|
+
* user-facing `defaultWorkspace` (an out-of-Home directory), and provider
|
|
8
|
+
* runtimes/backups were written to string-built siblings of Home
|
|
9
|
+
* (`${home}.task-runtimes`, `${home}-backups`). Both are unified here so that a
|
|
10
|
+
* single canonical YUI_HOME contains all self-managed data, with only the
|
|
11
|
+
* approved short-path IPC sockets (Controller, tmux, Agent Host, integration
|
|
12
|
+
* runtime) remaining outside it for the `sockaddr_un` length budget. Within the
|
|
13
|
+
* managed workspace root, storage v20 further collapses the former
|
|
14
|
+
* physical-worktree/symlink-view split into a single layer of real worktrees
|
|
15
|
+
* under `tasks/` (see {@link managedTaskRoot}).
|
|
16
|
+
*
|
|
17
|
+
* `defaultWorkspace` is intentionally NOT an input to this module: it remains a
|
|
18
|
+
* user-facing cwd for ad-hoc/global Roles and the origin for external Project
|
|
19
|
+
* input, never a second authority for internal managed layout.
|
|
20
|
+
*/
|
|
21
|
+
/** Canonicalize a Home string the same way callers already resolve it. */
|
|
22
|
+
function homeRoot(home) {
|
|
23
|
+
const resolved = resolve(home);
|
|
24
|
+
if (!isAbsolute(resolved)) {
|
|
25
|
+
throw new Error("YUI_HOME must be an absolute path.");
|
|
26
|
+
}
|
|
27
|
+
return resolved;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Root that contains every managed Git workspace. Since storage v20 this holds a
|
|
31
|
+
* single layer of real worktrees under `tasks/` (plus the legacy `worktree/`
|
|
32
|
+
* root, preserved by the 19->20 migration as its rollback anchor). A single
|
|
33
|
+
* parent keeps the managed families adjacent and lets the storage migrations
|
|
34
|
+
* relocate them as one subtree.
|
|
35
|
+
*/
|
|
36
|
+
export function managedWorkspacesRoot(home) {
|
|
37
|
+
return join(homeRoot(home), "workspaces");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* LEGACY physical worktree root: `<home>/workspaces/worktree`. Before storage
|
|
41
|
+
* v20 every managed clone/linked worktree lived here under a per-Project subtree,
|
|
42
|
+
* surfaced to each Task through a symlink view under `tasks/`. The 19->20
|
|
43
|
+
* migration collapses those into real worktrees under `tasks/` and PRESERVES this
|
|
44
|
+
* tree as the rollback anchor, so this path survives only for that migration's
|
|
45
|
+
* inline layout and for post-upgrade cleanup — no current runtime code derives a
|
|
46
|
+
* live worktree path from it.
|
|
47
|
+
*/
|
|
48
|
+
export function managedWorktreeRoot(home) {
|
|
49
|
+
return join(managedWorkspacesRoot(home), "worktree");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Managed Git worktrees, one real worktree per Task owner and Project at
|
|
53
|
+
* `<home>/workspaces/tasks/<taskId>/<owner>/<projectDirectory>` (owner being
|
|
54
|
+
* `main`, `work-items/<id>`, `reviews/<name>`, `integrations/<id>`, or
|
|
55
|
+
* `execution-lanes/<g>/<l>`). Before storage v20 these were symlink views over
|
|
56
|
+
* the physical `worktree/` root; the 19->20 migration makes them the worktrees
|
|
57
|
+
* themselves, so the logical entry path and the physical Git worktree coincide.
|
|
58
|
+
*/
|
|
59
|
+
export function managedTaskRoot(home) {
|
|
60
|
+
return join(managedWorkspacesRoot(home), "tasks");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Dedicated partition for provider runtime roots (data/cache/tmp) and the
|
|
64
|
+
* planning cwd. This is the ONE Home subtree the Task runtime isolation
|
|
65
|
+
* boundary permits its roots to overlap; every other part of Home (the
|
|
66
|
+
* database, `workspaces/`, `projects/`) stays protected.
|
|
67
|
+
*/
|
|
68
|
+
export function managedRuntimeRoot(home) {
|
|
69
|
+
return join(homeRoot(home), "runtime", "task-runtimes");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Dedicated Home partition for the integration check runtime's provider
|
|
73
|
+
* data/cache/tmp. Distinct from `managedRuntimeRoot` so an integration attempt's
|
|
74
|
+
* disposable roots never collide with a Task runtime's, yet still sit inside the
|
|
75
|
+
* one managed runtime subtree the isolation boundary permits to overlap Home.
|
|
76
|
+
* The integration tmux socket endpoint is NOT here — it stays a short `/tmp`
|
|
77
|
+
* path for the `sockaddr_un` budget (see `gitIntegrationService`).
|
|
78
|
+
*/
|
|
79
|
+
export function managedIntegrationRuntimeRoot(home) {
|
|
80
|
+
return join(homeRoot(home), "runtime", "integration-runtimes");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Disposable staging prefix for `yui update`'s side-by-side package install
|
|
84
|
+
* (an upgrade artifact): `<home>/runtime/update-staging`. Kept under Home so a
|
|
85
|
+
* staged upgrade never lands in a shared system temp root.
|
|
86
|
+
*/
|
|
87
|
+
export function updateStagingRoot(home) {
|
|
88
|
+
return join(homeRoot(home), "runtime", "update-staging");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Disposable scratch parent for the release workflow's Yui-authored artifacts —
|
|
92
|
+
* the fresh-install smoke directory and the verified publish-snapshot tarball
|
|
93
|
+
* (release artifacts): `<home>/runtime/release-workflow`. Kept under Home so a
|
|
94
|
+
* release artifact never lands in a shared system temp root.
|
|
95
|
+
*/
|
|
96
|
+
export function releaseWorkflowScratchRoot(home) {
|
|
97
|
+
return join(homeRoot(home), "runtime", "release-workflow");
|
|
98
|
+
}
|
|
99
|
+
/** Disposable planning cwd parent: `<home>/runtime/task-runtimes/planning`. */
|
|
100
|
+
export function planningRuntimeRoot(home) {
|
|
101
|
+
return join(managedRuntimeRoot(home), "planning");
|
|
102
|
+
}
|
|
103
|
+
/** Planning cwd for a single Task, materialized on demand at launch. */
|
|
104
|
+
export function planningRuntimeCwd(home, taskId) {
|
|
105
|
+
return join(planningRuntimeRoot(home), taskId);
|
|
106
|
+
}
|
|
107
|
+
/** Storage-upgrade database backups: `<home>/backups` (was a Home sibling). */
|
|
108
|
+
export function storageBackupRoot(home) {
|
|
109
|
+
return join(homeRoot(home), "backups");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Default working directory for Yui-owned Roles that carry no user-chosen,
|
|
113
|
+
* external cwd: the built-in Global Operator/Leader created by `yui setup` and
|
|
114
|
+
* ad-hoc Global Roles added without an explicit `--workspace`. Historically
|
|
115
|
+
* these fell back to a Home-sibling `workspace/` directory (or `process.cwd()`),
|
|
116
|
+
* which leaked Yui-managed scratch outside the canonical Home. A user who names
|
|
117
|
+
* an external directory keeps external-resource semantics; only the auto-created
|
|
118
|
+
* default lands here, under Home: `<home>/workspaces/global`.
|
|
119
|
+
*
|
|
120
|
+
* This is a plain cwd, NOT a managed Git workspace, so it is deliberately a
|
|
121
|
+
* sibling of `worktree/`+`tasks/` under `workspaces/`, never inside them.
|
|
122
|
+
*/
|
|
123
|
+
export function managedGlobalRoleWorkspace(home) {
|
|
124
|
+
return join(managedWorkspacesRoot(home), "global");
|
|
125
|
+
}
|
|
126
|
+
/** True when `child` is strictly inside `parent` (shared with isolation checks). */
|
|
127
|
+
export function isWithinManagedRoot(parent, child) {
|
|
128
|
+
const nested = relative(resolve(parent), resolve(child));
|
|
129
|
+
return nested.length > 0 && !nested.startsWith("..") && !isAbsolute(nested);
|
|
130
|
+
}
|