@zq-silk/yui 2.2.0 → 2.4.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 +19 -3
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +25 -11
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +30 -9
- package/dist/commands/taskIntegrationCommands.js +5 -16
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +51 -39
- package/dist/controller/clientRuntime.js +3 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +15 -79
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobCleanupInspection.js +67 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +86 -80
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +18 -11
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/integrationRuntimeIsolation.js +21 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskRuntimeIsolation.js +91 -21
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +306 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +114 -9
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +4 -3
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-leader/references/integration.md +20 -0
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +3 -0
- package/skills/yui-runtime/SKILL.md +4 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
- package/dist/web/assets/client/app.js +0 -804
- package/dist/web/assets/client/components.js +0 -418
- package/dist/web/assets/client/detail.js +0 -333
- package/dist/web/assets/client/dock.js +0 -225
- package/dist/web/assets/client/evidence.js +0 -172
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1161
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -63
- package/dist/web/assets/client/sections.js +0 -313
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -169
- package/dist/web/assets/styles/views.js +0 -292
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lstat, mkdir, readdir, readlink,
|
|
1
|
+
import { lstat, mkdir, readdir, readlink, rmdir, symlink, unlink } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { isDeepStrictEqual } from "node:util";
|
|
4
4
|
import { recordArchiveCleanup } from "../task/archiveDiagnostics.js";
|
|
@@ -25,7 +25,8 @@ import { NodeGitWorkspace, worktreeIdentity } from "./gitWorkspace.js";
|
|
|
25
25
|
import { acquireProjectMaintenanceLocks, ProjectMaintenanceLockCancelledError, ProjectMaintenanceLockedError } from "./projectMaintenanceLock.js";
|
|
26
26
|
import { captureTaskBaseProvenance, recordTaskBaseProvenanceEvents } from "./taskBaseFreshness.js";
|
|
27
27
|
import { generateTaskWorkspaceIdentity, taskWorkspaceRefSegment, validateTaskWorkspaceIdentity } from "./taskWorkspaceIdentity.js";
|
|
28
|
-
import { inspectWorkspaceCleanup } from "./workspaceCleanupInspection.js";
|
|
28
|
+
import { inspectWorkspaceCleanup, inspectWorkspaceContainer } from "./workspaceCleanupInspection.js";
|
|
29
|
+
import { defaultIntegrationRuntimeIsolation } from "../runtime/integrationRuntimeIsolation.js";
|
|
29
30
|
const MAIN_WORKTREE = "main";
|
|
30
31
|
const LEADER_ROLE = "leader";
|
|
31
32
|
/**
|
|
@@ -73,7 +74,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
73
74
|
this.maintenanceLockOptions = maintenanceLockOptions;
|
|
74
75
|
}
|
|
75
76
|
inspectWorkspaceCleanup(workspace, disposition, forceArchive = false) {
|
|
76
|
-
return inspectWorkspaceCleanup(this.store, this.git, workspace, disposition, forceArchive);
|
|
77
|
+
return inspectWorkspaceCleanup(this.store, this.git, workspace, disposition, forceArchive, this.home);
|
|
77
78
|
}
|
|
78
79
|
async #assertWorkspaceCleanup(workspace, disposition) {
|
|
79
80
|
const checks = await this.inspectWorkspaceCleanup(workspace, disposition);
|
|
@@ -1296,7 +1297,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
1296
1297
|
this.#recordArchivePathRemoval(task, entry.path, result);
|
|
1297
1298
|
removed ||= result === "removed";
|
|
1298
1299
|
}
|
|
1299
|
-
await removeWorkspaceView(workspace.root);
|
|
1300
|
+
await removeWorkspaceView(workspace.root, workspace);
|
|
1300
1301
|
this.#resourceRegistrar().markWorkspaceDeleted(workspace);
|
|
1301
1302
|
this.store.removeManagedWorkspace(workspace.owner);
|
|
1302
1303
|
return removed ? "removed" : "missing";
|
|
@@ -1907,7 +1908,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
1907
1908
|
this.#recordArchivePathRemoval(task, entry.path, result);
|
|
1908
1909
|
removed ||= result === "removed";
|
|
1909
1910
|
}
|
|
1910
|
-
await removeWorkspaceView(workspace.root);
|
|
1911
|
+
await removeWorkspaceView(workspace.root, workspace);
|
|
1911
1912
|
this.#resourceRegistrar().markWorkspaceDeleted(workspace);
|
|
1912
1913
|
this.store.transaction((tx) => {
|
|
1913
1914
|
const currentRound = tx.getReviewRound(task.id, round.id);
|
|
@@ -1976,7 +1977,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
1976
1977
|
this.#recordArchivePathRemoval(task, entry.path, result);
|
|
1977
1978
|
removed ||= result === "removed";
|
|
1978
1979
|
}
|
|
1979
|
-
await removeWorkspaceView(workspace.root);
|
|
1980
|
+
await removeWorkspaceView(workspace.root, workspace);
|
|
1980
1981
|
this.#resourceRegistrar().markWorkspaceDeleted(workspace);
|
|
1981
1982
|
try {
|
|
1982
1983
|
this.#recordWorkspaceRemoval(task, workspace, this.#fallbackWorkspace(), {
|
|
@@ -2027,39 +2028,36 @@ export class FileTaskWorkspacePreparer {
|
|
|
2027
2028
|
throw new Error(`IntegrationAttempt must be terminal before cleanup: ${attempt.id}.`);
|
|
2028
2029
|
}
|
|
2029
2030
|
const workspace = this.store.getIntegrationWorkspace(task.id, attempt.id);
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2031
|
+
if (workspace === null)
|
|
2032
|
+
return "missing"; // no current resource ownership to delete
|
|
2033
|
+
// Terminal history does not authorize discarding conflict resolutions or
|
|
2034
|
+
// diagnostics. All entry points use the same exact-owner inspection.
|
|
2035
|
+
const runtime = options.runtimeIsolation ?? defaultIntegrationRuntimeIsolation(this.home, this.store.getHomeIdentity().homeId);
|
|
2036
|
+
const checks = await inspectWorkspaceCleanup(this.store, this.git, workspace, "abandoned", false, this.home, runtime);
|
|
2037
|
+
if (checks.length > 0)
|
|
2038
|
+
throw new CleanupInspectionError(checks);
|
|
2039
|
+
runtime.cleanup(runtime.preflight({ workspace, allowExactActive: true }), attempt.status === "committed" ? "completion" : "failure");
|
|
2035
2040
|
const project = requireProject(this.store, attempt.projectId);
|
|
2036
|
-
//
|
|
2037
|
-
//
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
const directory = recorded?.directory
|
|
2042
|
-
?? task.projectBindings.find(({ projectId }) => projectId === attempt.projectId)?.directory;
|
|
2043
|
-
if (directory === undefined) {
|
|
2044
|
-
throw new Error(`Integration Project binding is unavailable: ${task.id}/${project.id}.`);
|
|
2041
|
+
// Only the recorded entry grants resource ownership; no history-derived
|
|
2042
|
+
// path reconstruction after the current resource record has been removed.
|
|
2043
|
+
const recorded = workspace.entries[0];
|
|
2044
|
+
if (recorded === undefined || recorded.projectId !== project.id || workspace.entries.length !== 1) {
|
|
2045
|
+
throw new Error(`Integration workspace Project identity is unavailable: ${task.id}/${project.id}.`);
|
|
2045
2046
|
}
|
|
2046
2047
|
const result = await this.git.removeIntegrationWorktree({
|
|
2047
2048
|
repositoryPath: this.#taskRepositoryPath(task.id, project.id),
|
|
2048
|
-
container: recorded
|
|
2049
|
-
|
|
2050
|
-
: this.#integrationWorkspaceRoot(task.id, attempt.id),
|
|
2051
|
-
directory,
|
|
2049
|
+
container: dirname(recorded.path),
|
|
2050
|
+
directory: recorded.directory,
|
|
2052
2051
|
taskSegment: this.#taskSegment(task),
|
|
2053
2052
|
integrationId: attempt.id,
|
|
2054
|
-
discardChanges:
|
|
2053
|
+
discardChanges: false
|
|
2055
2054
|
});
|
|
2056
2055
|
if (result !== "dirty") {
|
|
2057
|
-
for (const entry of workspace
|
|
2056
|
+
for (const entry of workspace.entries) {
|
|
2058
2057
|
this.#recordArchivePathRemoval(task, entry.path, result);
|
|
2059
2058
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
await rm(join(this.home, "artifacts", "integration-checks", task.id, attempt.id), { recursive: true, force: true });
|
|
2059
|
+
this.#resourceRegistrar().markWorkspaceDeleted(workspace);
|
|
2060
|
+
// Check logs are stable historical evidence, not worktree scratch.
|
|
2063
2061
|
this.store.removeManagedWorkspace({
|
|
2064
2062
|
type: "integration-attempt",
|
|
2065
2063
|
taskId: task.id,
|
|
@@ -2097,15 +2095,9 @@ export class FileTaskWorkspacePreparer {
|
|
|
2097
2095
|
if (main !== null) {
|
|
2098
2096
|
await this.#assertWorkspaceCleanup(main, disposition);
|
|
2099
2097
|
assertTaskArchiveState(requireTask(this.store, task.id), task);
|
|
2100
|
-
//
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
taskId,
|
|
2104
|
-
status: "retained-dirty",
|
|
2105
|
-
...(task.cwd === undefined ? {} : { path: task.cwd })
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
for (const entry of main.entries) {
|
|
2098
|
+
// The shared preflight and removeTaskClone both inspect clone identity.
|
|
2099
|
+
// Do not re-enter linked-worktree inspection against a missing clone.
|
|
2100
|
+
for (const entry of main.entries.filter(e => e.access === "write")) {
|
|
2109
2101
|
assertTaskArchiveState(requireTask(this.store, task.id), task);
|
|
2110
2102
|
const result = await this.git.removeTaskClone({
|
|
2111
2103
|
path: entry.path, container: main.root, directory: entry.directory,
|
|
@@ -2117,7 +2109,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
2117
2109
|
this.#recordArchivePathRemoval(task, entry.path, result);
|
|
2118
2110
|
}
|
|
2119
2111
|
assertTaskArchiveState(requireTask(this.store, task.id), task);
|
|
2120
|
-
await removeWorkspaceView(main.root);
|
|
2112
|
+
await removeWorkspaceView(main.root, main);
|
|
2121
2113
|
this.#resourceRegistrar().markWorkspaceDeleted(main);
|
|
2122
2114
|
assertTaskArchiveState(requireTask(this.store, task.id), task);
|
|
2123
2115
|
this.#recordWorkspaceRemoval(task, main, this.#fallbackWorkspace());
|
|
@@ -2175,9 +2167,6 @@ export class FileTaskWorkspacePreparer {
|
|
|
2175
2167
|
#reviewRoundWorkspaceRoot(taskId, round) {
|
|
2176
2168
|
return join(resolveTaskRoot(this.home), safePathSegment(taskId), "reviews", safePathSegment(this.#reviewWorktreeName(round)));
|
|
2177
2169
|
}
|
|
2178
|
-
#integrationWorkspaceRoot(taskId, integrationId) {
|
|
2179
|
-
return integrationWorkspaceRoot(this.home, taskId, integrationId);
|
|
2180
|
-
}
|
|
2181
2170
|
#reviewWorktreeName(round) {
|
|
2182
2171
|
return round.scope === "task"
|
|
2183
2172
|
? `reviewer-${round.reviewerRoleName}`
|
|
@@ -2570,7 +2559,12 @@ async function ensureWorkspaceView(root, entries) {
|
|
|
2570
2559
|
await symlink(target, join(root, directory), "dir");
|
|
2571
2560
|
}
|
|
2572
2561
|
}
|
|
2573
|
-
async function removeWorkspaceView(root) {
|
|
2562
|
+
async function removeWorkspaceView(root, workspace) {
|
|
2563
|
+
if (workspace !== undefined) {
|
|
2564
|
+
const checks = await inspectWorkspaceContainer(workspace);
|
|
2565
|
+
if (checks.length > 0)
|
|
2566
|
+
throw new CleanupInspectionError(checks);
|
|
2567
|
+
}
|
|
2574
2568
|
let entries;
|
|
2575
2569
|
try {
|
|
2576
2570
|
entries = await readdir(root, { withFileTypes: true });
|
|
@@ -2584,6 +2578,8 @@ async function removeWorkspaceView(root) {
|
|
|
2584
2578
|
if (!entry.isSymbolicLink()) {
|
|
2585
2579
|
throw new Error(`Managed workspace contains an unexpected entry: ${join(root, entry.name)}.`);
|
|
2586
2580
|
}
|
|
2581
|
+
}
|
|
2582
|
+
for (const entry of entries) {
|
|
2587
2583
|
await unlink(join(root, entry.name));
|
|
2588
2584
|
}
|
|
2589
2585
|
await rmdir(root);
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
1
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { lstat, readdir, readlink } from "node:fs/promises";
|
|
3
4
|
import { projectTaskRemoteDeliveryFromStore } from "../task/remoteDeliveryService.js";
|
|
4
5
|
import { cleanupCheckFromError } from "../workspace/cleanupInspection.js";
|
|
5
6
|
import { WorkItemChangeSetManager } from "../workspace/workItemChangeSetManager.js";
|
|
6
7
|
import { managedWorkspaceKey, managedWorktreeName } from "../worktree/managedWorkspace.js";
|
|
7
8
|
import { integrationWorktreeIdentity, worktreeIdentity } from "./gitWorkspace.js";
|
|
8
9
|
import { taskWorkspaceRefSegment } from "./taskWorkspaceIdentity.js";
|
|
10
|
+
import { jobCleanupBlocker } from "../job/jobCleanupInspection.js";
|
|
11
|
+
import { defaultIntegrationRuntimeIsolation } from "../runtime/integrationRuntimeIsolation.js";
|
|
12
|
+
import { inspectTaskRuntimeCleanup } from "../runtime/taskRuntimeIsolation.js";
|
|
9
13
|
/** Current owner facts + the same Git inspection used immediately before removal.
|
|
10
14
|
* No preparation, locks, Git refresh, runtime stop, DB writes or repair.
|
|
11
15
|
*/
|
|
12
|
-
export async function inspectWorkspaceCleanup(store, git, workspace, disposition, forceArchive = false) {
|
|
16
|
+
export async function inspectWorkspaceCleanup(store, git, workspace, disposition, forceArchive = false, home, integrationRuntime) {
|
|
13
17
|
const owner = workspace.owner;
|
|
14
18
|
const task = store.getTask(owner.taskId);
|
|
15
19
|
if (task === null)
|
|
@@ -23,6 +27,18 @@ export async function inspectWorkspaceCleanup(store, git, workspace, disposition
|
|
|
23
27
|
add("workspace-identity-mismatch", "Current workspace ownership differs from the inspected record.", "same managed workspace", "changed or missing");
|
|
24
28
|
return checks;
|
|
25
29
|
}
|
|
30
|
+
// Job admission supports these three exact managed owners. Apply the same
|
|
31
|
+
// physical check before every supported owner's cleanup, not only Integration.
|
|
32
|
+
for (const job of store.listDurableJobs(task.id).filter(job => (owner.type === "task" && job.owner.kind === "task")
|
|
33
|
+
|| (owner.type === "work-item" && job.owner.kind === "work-item"
|
|
34
|
+
&& job.owner.workItemId === owner.workItemId)
|
|
35
|
+
|| (owner.type === "integration-attempt" && job.owner.kind === "integration-attempt"
|
|
36
|
+
&& job.owner.integrationAttemptId === owner.integrationAttemptId))) {
|
|
37
|
+
const blocker = jobCleanupBlocker(job, home);
|
|
38
|
+
if (blocker !== undefined)
|
|
39
|
+
add("unresolved-execution", blocker, "settled execution", { jobId: job.id, status: job.status });
|
|
40
|
+
}
|
|
41
|
+
checks.push(...await inspectWorkspaceContainer(workspace));
|
|
26
42
|
if (task.workspaceIdentity === undefined && workspace.entries.some(e => e.access === "write")) {
|
|
27
43
|
add("workspace-metadata-missing", "Task has no durable workspace identity; do not infer one from paths.", "recorded Task workspace identity", null);
|
|
28
44
|
return checks;
|
|
@@ -68,9 +84,24 @@ export async function inspectWorkspaceCleanup(store, git, workspace, disposition
|
|
|
68
84
|
}
|
|
69
85
|
case "integration-attempt": {
|
|
70
86
|
const attempt = store.getIntegrationAttempt(task.id, owner.integrationAttemptId);
|
|
87
|
+
const entry = workspace.entries[0];
|
|
88
|
+
if (workspace.entries.length !== 1 || entry?.access !== "write"
|
|
89
|
+
|| entry.projectId !== attempt?.projectId || entry.path !== workspace.root) {
|
|
90
|
+
add("workspace-identity-mismatch", "Integration resource does not match its single recorded Project worktree.", "one writable Project at the Integration root", "different or missing entry");
|
|
91
|
+
return checks;
|
|
92
|
+
}
|
|
71
93
|
if (attempt === null || ["running", "blocked", "conflicted", "validating"].includes(attempt.status)) {
|
|
72
94
|
add("owner-unsettled", "IntegrationAttempt must be terminal before cleanup.", "terminal attempt", attempt?.status ?? null);
|
|
73
95
|
}
|
|
96
|
+
try {
|
|
97
|
+
if (home === undefined)
|
|
98
|
+
throw new Error("Runtime Home unavailable.");
|
|
99
|
+
const runtime = integrationRuntime ?? defaultIntegrationRuntimeIsolation(home, store.getHomeIdentity().homeId);
|
|
100
|
+
inspectTaskRuntimeCleanup(runtime.preflight({ workspace, allowExactActive: true }), attempt?.status === "committed" ? "completion" : "failure");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
add("runtime-resource-unverified", "Integration runtime ownership or physical quiescence cannot be verified.", "exactly owned inactive runtime and cleanup claims", "unverified");
|
|
104
|
+
}
|
|
74
105
|
break;
|
|
75
106
|
}
|
|
76
107
|
case "task":
|
|
@@ -79,9 +110,9 @@ export async function inspectWorkspaceCleanup(store, git, workspace, disposition
|
|
|
79
110
|
}
|
|
80
111
|
break;
|
|
81
112
|
}
|
|
82
|
-
const delivery = owner.type === "task"
|
|
113
|
+
const delivery = owner.type === "task"
|
|
83
114
|
? await readArchiveDelivery(store, git, task, forceArchive) : undefined;
|
|
84
|
-
if (delivery !== undefined && (!delivery.allMerged || !delivery.allVerified)) {
|
|
115
|
+
if (disposition === "integrated" && delivery !== undefined && (!delivery.allMerged || !delivery.allVerified)) {
|
|
85
116
|
add("delivery-coverage", "Task main has commits without verified remote coverage; cleanup does not establish delivery.", { allMerged: true, allVerified: true }, { allMerged: delivery.allMerged, allVerified: delivery.allVerified });
|
|
86
117
|
}
|
|
87
118
|
for (const entry of workspace.entries.filter(e => e.access === "write")) {
|
|
@@ -116,14 +147,20 @@ export async function inspectWorkspaceCleanup(store, git, workspace, disposition
|
|
|
116
147
|
? await git.inspectTaskClone({ path: entry.path, container: workspace.root, directory: entry.directory,
|
|
117
148
|
taskSegment, branch: entry.branch })
|
|
118
149
|
: await git.inspectWorktree({ repositoryPath: main.path, container, directory: entry.directory,
|
|
119
|
-
taskSegment, roleName,
|
|
150
|
+
taskSegment, roleName,
|
|
151
|
+
...(owner.type === "integration-attempt" ? { expectedBranch } : {}) });
|
|
120
152
|
if (state === "dirty")
|
|
121
153
|
check("dirty-worktree", "Uncommitted or untracked changes must be preserved.", "clean", "dirty");
|
|
122
154
|
if (owner.type === "task" && state !== "missing" && delivery !== undefined) {
|
|
123
155
|
const covered = delivery.projects.find(p => p.projectId === entry.projectId);
|
|
124
156
|
const expected = covered?.expectedLocalCommit;
|
|
125
157
|
const observed = (await git.inspect(entry.path, "HEAD")).baseCommit;
|
|
126
|
-
|
|
158
|
+
const baseline = task.projectBindings.find(binding => binding.projectId === entry.projectId)?.baseCommit;
|
|
159
|
+
const unchangedAbandonment = disposition === "abandoned" && baseline !== undefined && observed === baseline;
|
|
160
|
+
if (!unchangedAbandonment && disposition === "abandoned" && (!covered?.merged || !covered.verified)) {
|
|
161
|
+
check("delivery-coverage", "Abandonment does not discard unverified local commits; only the exact unchanged baseline is disposable.", { baseline: baseline ?? null, verifiedDelivery: true }, observed);
|
|
162
|
+
}
|
|
163
|
+
else if (!unchangedAbandonment && expected != null && expected !== observed && covered?.deliveryLocalCommit !== observed) {
|
|
127
164
|
check("head-mismatch", "Task main HEAD differs from the accepted head and its covered publication candidate.", { acceptedCommit: expected, deliveryLocalCommit: covered?.deliveryLocalCommit ?? null }, observed);
|
|
128
165
|
}
|
|
129
166
|
}
|
|
@@ -136,6 +173,49 @@ export async function inspectWorkspaceCleanup(store, git, workspace, disposition
|
|
|
136
173
|
}
|
|
137
174
|
return checks;
|
|
138
175
|
}
|
|
176
|
+
/** Inspect the complete logical container before removing any Project. Files
|
|
177
|
+
* and unrecognized links are evidence, not disposable workspace scaffolding.
|
|
178
|
+
*/
|
|
179
|
+
export async function inspectWorkspaceContainer(workspace) {
|
|
180
|
+
if (workspace.owner.type === "integration-attempt")
|
|
181
|
+
return []; // root is the Git worktree itself
|
|
182
|
+
const resource = managedWorkspaceKey(workspace.owner);
|
|
183
|
+
const sources = [resource];
|
|
184
|
+
const actions = workspaceCleanupActions(workspace);
|
|
185
|
+
try {
|
|
186
|
+
const root = await lstat(workspace.root).catch(error => {
|
|
187
|
+
if (error.code === "ENOENT")
|
|
188
|
+
return null;
|
|
189
|
+
throw error;
|
|
190
|
+
});
|
|
191
|
+
if (root === null)
|
|
192
|
+
return [];
|
|
193
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
194
|
+
return [{
|
|
195
|
+
resource, reason: "workspace-identity-mismatch", status: "blocked",
|
|
196
|
+
detail: "Logical workspace container is not an owned directory.",
|
|
197
|
+
expected: "directory", observed: "different filesystem type", sources, actions
|
|
198
|
+
}];
|
|
199
|
+
const unexpected = [];
|
|
200
|
+
for (const entry of await readdir(workspace.root, { withFileTypes: true })) {
|
|
201
|
+
const owned = workspace.entries.find(e => e.directory === entry.name);
|
|
202
|
+
const allowed = owned !== undefined && (owned.access === "write"
|
|
203
|
+
? entry.isDirectory() && !entry.isSymbolicLink()
|
|
204
|
+
: entry.isSymbolicLink() && resolve(workspace.root, await readlink(join(workspace.root, entry.name))) === resolve(owned.path));
|
|
205
|
+
if (!allowed)
|
|
206
|
+
unexpected.push(entry.name);
|
|
207
|
+
}
|
|
208
|
+
return unexpected.length === 0 ? [] : [{
|
|
209
|
+
resource, reason: "workspace-material-retained", status: "blocked",
|
|
210
|
+
detail: "Logical container contains material outside its registered Project entries; no Project removal was started.",
|
|
211
|
+
expected: "only registered Project directories/links", observed: unexpected,
|
|
212
|
+
sources, actions
|
|
213
|
+
}];
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return cleanupCheckFromError(error, resource, sources, actions);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
139
219
|
/** Reuse Publication's projection, supplying live heads only where its existing
|
|
140
220
|
* contract asks for them (active/cancelled). Completed/archived heads stay frozen.
|
|
141
221
|
* Never prepare a workspace or substitute an old binding's commit for a failed read.
|
|
@@ -166,7 +246,8 @@ export async function readArchiveDelivery(store, git, task, forceArchive = false
|
|
|
166
246
|
export function workspaceCleanupActions(workspace) {
|
|
167
247
|
const owner = workspace.owner;
|
|
168
248
|
switch (owner.type) {
|
|
169
|
-
case "task": return [`yui task remote-delivery ${owner.taskId}`, `yui task show ${owner.taskId}
|
|
249
|
+
case "task": return [`yui task remote-delivery ${owner.taskId}`, `yui task show ${owner.taskId}`,
|
|
250
|
+
`yui task archive-cleanup ${owner.taskId}`];
|
|
170
251
|
case "work-item": return [`yui task work show ${owner.taskId}/${owner.workItemId}`,
|
|
171
252
|
`yui task work cleanup ${owner.taskId}/${owner.workItemId} --integrated|--abandon`];
|
|
172
253
|
case "review-round": return [`yui task work review show ${owner.taskId}/${owner.reviewRoundId}`,
|
|
@@ -10,6 +10,7 @@ import { execFile } from "node:child_process";
|
|
|
10
10
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
11
11
|
import { basename, join, resolve } from "node:path";
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
|
+
import { readGitWorktrees } from "../repository/gitWorktreeInventory.js";
|
|
13
14
|
import { managedProjectPath } from "../repository/project.js";
|
|
14
15
|
import { managedRuntimeRoot } from "../storage/homeLayout.js";
|
|
15
16
|
import { isResourceQuarantinePath } from "./resourceRegistry.js";
|
|
@@ -54,8 +55,7 @@ export function parseGitWorktreePorcelain(output) {
|
|
|
54
55
|
}
|
|
55
56
|
async function listGitWorktrees(repositoryPath) {
|
|
56
57
|
try {
|
|
57
|
-
|
|
58
|
-
return parseGitWorktreePorcelain(stdout);
|
|
58
|
+
return await readGitWorktrees(repositoryPath);
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
61
|
throw new Error(`Failed to list Git worktrees for ${repositoryPath}: `
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - `restoreAllResourceGc` is the explicit rollback entry point.
|
|
17
17
|
*/
|
|
18
18
|
import { execFileSync } from "node:child_process";
|
|
19
|
+
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
19
20
|
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
20
21
|
import { dirname, join, resolve, relative, isAbsolute } from "node:path";
|
|
21
22
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -384,13 +385,14 @@ function quarantineResource(home, record, now) {
|
|
|
384
385
|
const receiptPath = `${quarantinePath}.receipt.json`;
|
|
385
386
|
try {
|
|
386
387
|
if (isGitWorktreePath(record.path)) {
|
|
388
|
+
const program = supportedGit();
|
|
387
389
|
// Move the linked worktree, including its exact checkout and Git
|
|
388
390
|
// metadata, into the Home-local quarantine. Restore is the inverse
|
|
389
391
|
// move, so the recorded HEAD cannot drift with a branch.
|
|
390
392
|
mkdirSync(quarantineRoot, { recursive: true, mode: 0o700 });
|
|
391
393
|
// Keep the existing SQLite writer fence through this bounded move, so a
|
|
392
394
|
// Task reopen or new durable owner cannot commit between check and move.
|
|
393
|
-
execFileSync(
|
|
395
|
+
execFileSync(program.path, ["-C", record.path, "worktree", "move", "--", record.path, quarantinePath], { timeout: 30_000 });
|
|
394
396
|
writeQuarantineReceipt(home, record, now, "move", receiptPath);
|
|
395
397
|
return {
|
|
396
398
|
ok: true,
|
|
@@ -496,9 +498,10 @@ function restoreQuarantinedRecord(record, liveRefs, now) {
|
|
|
496
498
|
updatedAt: now.toISOString()
|
|
497
499
|
};
|
|
498
500
|
}
|
|
501
|
+
const program = isGitWorktreePath(quarantine.path) ? supportedGit() : undefined;
|
|
499
502
|
mkdirSync(dirname(quarantine.originalPath), { recursive: true });
|
|
500
|
-
if (
|
|
501
|
-
execFileSync(
|
|
503
|
+
if (program !== undefined) {
|
|
504
|
+
execFileSync(program.path, ["-C", quarantine.path, "worktree", "move", "--", quarantine.path, quarantine.originalPath], { timeout: 30_000 });
|
|
502
505
|
}
|
|
503
506
|
else {
|
|
504
507
|
renameSync(quarantine.path, quarantine.originalPath);
|
|
@@ -604,7 +607,7 @@ export async function purgeResourceQuarantine(home, options, store) {
|
|
|
604
607
|
try {
|
|
605
608
|
if (existsSync(quarantine.path)) {
|
|
606
609
|
if (isGitWorktreePath(quarantine.path)) {
|
|
607
|
-
execFileSync(
|
|
610
|
+
execFileSync(supportedGit().path, ["-C", quarantine.path, "worktree", "remove", "--force", "--", quarantine.path], { timeout: 30_000 });
|
|
608
611
|
}
|
|
609
612
|
rmSync(quarantine.path, { recursive: true, force: true });
|
|
610
613
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { execFileSync } from "node:child_process";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { basename, dirname, resolve } from "node:path";
|
|
12
|
+
import { gitOutputLine, supportedGit } from "../repository/gitCompatibility.js";
|
|
12
13
|
import { upsertResourceRecord } from "./resourceRegistry.js";
|
|
13
14
|
import { createResourceRegistryStore } from "./resourceRegistryStore.js";
|
|
14
15
|
import { createResourceRecord, isReleaseNamespacePath, resourceId } from "./resourceTypes.js";
|
|
@@ -100,15 +101,17 @@ export class ResourceRegistrar {
|
|
|
100
101
|
})]);
|
|
101
102
|
}
|
|
102
103
|
markWorkspaceDeleted(workspace) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
// Callers remove the managed owner only after this succeeds. Losing the
|
|
105
|
+
// receipt must retain that exact owner so a missing-path retry can finish.
|
|
106
|
+
this.#markPathsDeleted([
|
|
107
|
+
workspace.root,
|
|
108
|
+
...workspace.entries.filter(entry => entry.access === "write").map(entry => entry.path)
|
|
109
|
+
], ownerFromManagedWorkspace(this.#home, workspace.owner));
|
|
110
|
+
}
|
|
111
|
+
markTaskRuntimeDeleted(descriptor) {
|
|
112
|
+
// The validated descriptor and matching registry owner survive deletion.
|
|
113
|
+
// A missing-root retry may settle this receipt, never a different owner.
|
|
114
|
+
this.#markPathsDeleted(Object.values(descriptor.roots), ownerFromManagedWorkspace(this.#home, descriptor.workspace.owner));
|
|
112
115
|
}
|
|
113
116
|
markPathsDeleted(paths) {
|
|
114
117
|
try {
|
|
@@ -118,7 +121,7 @@ export class ResourceRegistrar {
|
|
|
118
121
|
// Best-effort receipt; path deletion must proceed regardless.
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
|
-
#markPathsDeleted(paths) {
|
|
124
|
+
#markPathsDeleted(paths, owner) {
|
|
122
125
|
const targets = new Set(paths.map((path) => resolve(path)));
|
|
123
126
|
const store = createResourceRegistryStore(this.#home);
|
|
124
127
|
try {
|
|
@@ -128,6 +131,10 @@ export class ResourceRegistrar {
|
|
|
128
131
|
for (const record of Object.values(state.records)) {
|
|
129
132
|
if (!targets.has(record.path) || record.disposition === "deleted")
|
|
130
133
|
continue;
|
|
134
|
+
if (owner !== undefined && ["home", "taskId", "workItemId", "reviewRoundId", "integrationAttemptId"]
|
|
135
|
+
.some(key => record.owner[key] !== owner[key])) {
|
|
136
|
+
throw new Error("Resource registration owner changed; deletion receipt retained for inspection.");
|
|
137
|
+
}
|
|
131
138
|
next = upsertResourceRecord(next, {
|
|
132
139
|
...record,
|
|
133
140
|
activeRefs: Object.freeze([]),
|
|
@@ -206,7 +213,7 @@ function ownerFromManagedWorkspace(home, owner, projectId) {
|
|
|
206
213
|
};
|
|
207
214
|
}
|
|
208
215
|
function readGitWorktreeMetadata(path) {
|
|
209
|
-
const commonDir = execFileSync(
|
|
216
|
+
const commonDir = resolve(path, gitOutputLine(execFileSync(supportedGit().path, ["-C", path, "rev-parse", "--git-common-dir"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })));
|
|
210
217
|
const repositoryPath = basename(commonDir) === ".git" ? dirname(commonDir) : commonDir;
|
|
211
218
|
let branch;
|
|
212
219
|
try {
|
|
@@ -41,6 +41,12 @@ export function createAgentEndpointFactory(start = startStructuredProviderSessio
|
|
|
41
41
|
endpoint.observe(event);
|
|
42
42
|
};
|
|
43
43
|
const opened = await start(payload, {
|
|
44
|
+
// The native TUI owns rendering while attached. Raw JSON mirroring into
|
|
45
|
+
// its inherited PTY would corrupt its screen on every Provider event.
|
|
46
|
+
mirrorOutput: (stream, text) => {
|
|
47
|
+
if (!endpoint?.nativeAccess?.attached)
|
|
48
|
+
(stream === "stdout" ? process.stdout : process.stderr).write(text);
|
|
49
|
+
},
|
|
44
50
|
onAccepted: (value) => emit({ type: "accepted", value }),
|
|
45
51
|
onActivity: (value) => emit({ type: "activity", value }),
|
|
46
52
|
onStarted: (value) => emit({ type: "started", value }),
|
|
@@ -74,6 +80,7 @@ function freezeConfiguration(value) {
|
|
|
74
80
|
class BuiltinAgentEndpoint {
|
|
75
81
|
driver;
|
|
76
82
|
configuration;
|
|
83
|
+
get nativeAccess() { return this.driver.nativeAccess; }
|
|
77
84
|
get ownedProcessId() { return this.driver.ownedProcessId; }
|
|
78
85
|
get nativeAccountHome() { return this.driver.nativeAccountHome; }
|
|
79
86
|
capabilities;
|
|
@@ -118,6 +125,7 @@ class BuiltinAgentEndpoint {
|
|
|
118
125
|
get nativeSessionId() { return this.driver.nativeSessionId; }
|
|
119
126
|
get conversationId() { return this.driver.conversationId; }
|
|
120
127
|
get processInstanceId() { return this.driver.processInstanceId; }
|
|
128
|
+
get publicReply() { return this.driver.publicReply; }
|
|
121
129
|
/**
|
|
122
130
|
* Delegated rather than copied at construction: the Session reads its own
|
|
123
131
|
* current state, and a value captured here would freeze the configuration as
|