@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
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import Database from "better-sqlite3";
|
|
3
3
|
import { BASELINE_SCHEMA_SQL, SQLITE_SCHEMA_TABLES } from "./baselineSchema.js";
|
|
4
4
|
import { migrateTaskMainWorkspaces } from "./upgrade/taskMainWorkspaceMigration.js";
|
|
5
|
+
import { migrateProjectKnowledge } from "./upgrade/projectKnowledgeMigration.js";
|
|
5
6
|
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION, STORAGE_FORMAT, isMinorStorageUpgrade, isStorageVersion, storageVersionParts } from "./storageVersions.js";
|
|
6
7
|
export { BASELINE_SCHEMA_SQL, SQLITE_SCHEMA_TABLES };
|
|
7
8
|
export const TELEMETRY_KEEP_PER_RUN = 200;
|
|
@@ -34,6 +35,47 @@ const MINOR_UPGRADES = Object.freeze([{
|
|
|
34
35
|
// New grants can carry source evidence. Valid historical Operator grants
|
|
35
36
|
// remain unchanged; migration must not invent authorization provenance.
|
|
36
37
|
sql: ""
|
|
38
|
+
}, {
|
|
39
|
+
fromVersion: "1.2",
|
|
40
|
+
toVersion: "1.3",
|
|
41
|
+
name: "selected-session-input",
|
|
42
|
+
introducedIn: "next",
|
|
43
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
44
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
45
|
+
// Optional explicit Session fences apply only to new inputs. Preserve old
|
|
46
|
+
// Role-addressed messages; never infer a historical user's Session choice.
|
|
47
|
+
sql: ""
|
|
48
|
+
}, {
|
|
49
|
+
fromVersion: "1.3",
|
|
50
|
+
toVersion: "1.4",
|
|
51
|
+
name: "project-knowledge-applicability",
|
|
52
|
+
introducedIn: "next",
|
|
53
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
54
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
55
|
+
sql: "",
|
|
56
|
+
dataMigration: "project-knowledge"
|
|
57
|
+
}, {
|
|
58
|
+
fromVersion: "1.4",
|
|
59
|
+
toVersion: "1.5",
|
|
60
|
+
name: "frozen-skill-packages",
|
|
61
|
+
introducedIn: "next",
|
|
62
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
63
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
64
|
+
// Additive effective-launch/manifest package evidence. Historical envelopes
|
|
65
|
+
// and their digests remain unchanged: absence explicitly means entrypoint-only
|
|
66
|
+
// provenance, not an invitation to invent packages from today's source files.
|
|
67
|
+
// Existing Hosts retain their original inputs. Restoring such a Session fails
|
|
68
|
+
// with a bounded new-Session instruction; ordinary readers never backfill it.
|
|
69
|
+
sql: ""
|
|
70
|
+
}, {
|
|
71
|
+
fromVersion: "1.5",
|
|
72
|
+
toVersion: "1.6",
|
|
73
|
+
name: "runtime-cost-evidence",
|
|
74
|
+
introducedIn: "next",
|
|
75
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
76
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
77
|
+
// Optional sourced monetary observations; historical missing costs stay unknown.
|
|
78
|
+
sql: ""
|
|
37
79
|
}]);
|
|
38
80
|
export function storageMinorUpgradePlan(from) {
|
|
39
81
|
if (from === CURRENT_STORAGE_VERSION)
|
|
@@ -143,6 +185,8 @@ export function applySqliteMinorUpgrades(db, home, createdRoots = []) {
|
|
|
143
185
|
if (step.dataMigration === "task-main-workspace") {
|
|
144
186
|
migrateTaskMainWorkspaces(db, home, createdRoots);
|
|
145
187
|
}
|
|
188
|
+
if (step.dataMigration === "project-knowledge")
|
|
189
|
+
migrateProjectKnowledge(db);
|
|
146
190
|
const { major, minor } = storageVersionParts(step.toVersion);
|
|
147
191
|
db.prepare("UPDATE storage_schema SET major=?,minor=?,checksum=? WHERE id=1")
|
|
148
192
|
.run(major, minor, step.targetChecksum);
|
|
@@ -66,6 +66,7 @@ import { initializeSqliteSchema, validateSqliteSchema, SqliteSchemaError, TELEME
|
|
|
66
66
|
import { StorageSchemaError } from "./storageSchema.js";
|
|
67
67
|
import { STORAGE_FORMAT } from "./storageVersions.js";
|
|
68
68
|
import { queryTaskCatalog } from "./taskCatalog.js";
|
|
69
|
+
import { queryTaskSearch } from "./taskSearch.js";
|
|
69
70
|
import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, executionLaneActiveRunKey, executionLaneActiveRunKeyParts, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, pendingWakeupProjection, StorageCancelledError, StorageConflictError, StorageRecordError, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, validateYuiConfig } from "./taskStore.js";
|
|
70
71
|
/** Read the immutable Home identity without opening a writable Store connection. */
|
|
71
72
|
export function readSqliteHomeIdentity(rootDir, databaseFilename = "yui.db") {
|
|
@@ -253,6 +254,55 @@ export class SqliteTaskStore {
|
|
|
253
254
|
listEventsByType(taskId, types) {
|
|
254
255
|
return this.#sortById(this.#listPayload("events", "task_id = ? AND type IN (SELECT value FROM json_each(?))", [taskId, JSON.stringify(types)]), event => event.id);
|
|
255
256
|
}
|
|
257
|
+
readTaskUsageFacts(taskId) {
|
|
258
|
+
// Bounded source selection, not a scan/deserialization of Task history.
|
|
259
|
+
// Strip result bodies before they cross the SQLite boundary.
|
|
260
|
+
// Each event type uses idx_events_type_time (including its rowid tie
|
|
261
|
+
// breaker); only bounded prefixes are merged before JSON projection.
|
|
262
|
+
const limit = 2000;
|
|
263
|
+
const rows = this.#db.prepare(`SELECT json_set(payload, '$.payload.observation',
|
|
264
|
+
CASE WHEN type = 'runtime.observation' THEN
|
|
265
|
+
json_remove(json_extract(payload, '$.payload.observation'),
|
|
266
|
+
'$.payload.output', '$.payload.input', '$.payload.failure.lastOutput', '$.payload.summary')
|
|
267
|
+
ELSE NULL END) AS payload
|
|
268
|
+
FROM (
|
|
269
|
+
SELECT * FROM (
|
|
270
|
+
SELECT * FROM (
|
|
271
|
+
SELECT rowid AS source_order, type, occurred_at, payload FROM events
|
|
272
|
+
WHERE task_id = ? AND type = 'runtime.observation'
|
|
273
|
+
ORDER BY occurred_at DESC, rowid DESC LIMIT ?
|
|
274
|
+
)
|
|
275
|
+
UNION ALL
|
|
276
|
+
SELECT * FROM (
|
|
277
|
+
SELECT rowid AS source_order, type, occurred_at, payload FROM events
|
|
278
|
+
WHERE task_id = ? AND type = 'task.cancelled'
|
|
279
|
+
ORDER BY occurred_at DESC, rowid DESC LIMIT ?
|
|
280
|
+
)
|
|
281
|
+
) ORDER BY occurred_at DESC, source_order DESC LIMIT ?
|
|
282
|
+
)`).all(taskId, limit + 1, taskId, limit + 1, limit + 1);
|
|
283
|
+
const events = rows.slice(0, limit).map(row => {
|
|
284
|
+
const event = JSON.parse(row.payload);
|
|
285
|
+
// json_set embeds normalized JSON as an object; the event wire contract
|
|
286
|
+
// stores the canonical observation in a string.
|
|
287
|
+
const observation = event.payload.observation;
|
|
288
|
+
return typeof observation === "object" && observation !== null
|
|
289
|
+
? { ...event, payload: { ...event.payload, observation: JSON.stringify(observation) } } : event;
|
|
290
|
+
});
|
|
291
|
+
// The Task/Run primary key supplies a deterministic bounded ID window,
|
|
292
|
+
// not a "latest by time" claim. Beyond the cap coverage is partial.
|
|
293
|
+
const runs = this.#db.prepare(`SELECT json_object(
|
|
294
|
+
'id', turn_id, 'taskId', task_id, 'roleName', json_extract(payload, '$.roleName'),
|
|
295
|
+
'workItemId', json_extract(payload, '$.workItemId'),
|
|
296
|
+
'effective', json_object('agentId', json_extract(payload, '$.effective.agentId'),
|
|
297
|
+
'adapterId', json_extract(payload, '$.effective.adapterId'))) AS payload
|
|
298
|
+
FROM (
|
|
299
|
+
SELECT turn_id, task_id, payload FROM turns WHERE task_id = ?
|
|
300
|
+
ORDER BY turn_id DESC LIMIT ?
|
|
301
|
+
)`)
|
|
302
|
+
.all(taskId, limit + 1);
|
|
303
|
+
return { events, runs: runs.slice(0, limit).map(row => JSON.parse(row.payload)),
|
|
304
|
+
complete: rows.length <= limit && runs.length <= limit };
|
|
305
|
+
}
|
|
256
306
|
/** Delivery needs original workspace bases, never complete Run reports. */
|
|
257
307
|
listTaskRunWorkspaceBases(taskId) {
|
|
258
308
|
return this.#db.prepare(`SELECT json_object(
|
|
@@ -978,6 +1028,9 @@ export class SqliteTaskStore {
|
|
|
978
1028
|
queryTaskCatalog(query) {
|
|
979
1029
|
return queryTaskCatalog(this.#db, query);
|
|
980
1030
|
}
|
|
1031
|
+
queryTaskSearch(query) {
|
|
1032
|
+
return queryTaskSearch(this.#db, query);
|
|
1033
|
+
}
|
|
981
1034
|
listTaskChoices() {
|
|
982
1035
|
const rows = this.#db.prepare(`SELECT c.task_id AS id, c.status,
|
|
983
1036
|
json_extract(r.payload, '$.title') AS title
|
package/dist/storage/storeRpc.js
CHANGED
|
@@ -53,8 +53,10 @@ const READ_ONLY_STORE_METHODS = new Set([
|
|
|
53
53
|
"readNextActionFacts",
|
|
54
54
|
"readCompletionReadinessFacts",
|
|
55
55
|
"queryContextRecords",
|
|
56
|
+
"queryTaskSearch",
|
|
56
57
|
"latestEventSequence",
|
|
57
58
|
"listEventsByType",
|
|
59
|
+
"readTaskUsageFacts",
|
|
58
60
|
"listTaskRunWorkspaceBases",
|
|
59
61
|
"contextInputReferences",
|
|
60
62
|
"listActiveRuns",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const TASK_SEARCH_KINDS = ["brief", "message", "decision", "result", "completion"];
|
|
2
|
+
/** A read-only view of authoritative JSON, not a persisted index. Apply Task
|
|
3
|
+
* scope before body matching and return only page-sized snippets/identities.
|
|
4
|
+
* No derived state needs rebuilding when a source changes or is removed. */
|
|
5
|
+
export function queryTaskSearch(db, input) {
|
|
6
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 101) {
|
|
7
|
+
throw new Error("Task search query exceeds its bounded contract.");
|
|
8
|
+
}
|
|
9
|
+
const branches = [
|
|
10
|
+
`SELECT task_id AS taskId, 'brief' AS kind, 'task-brief' AS store, task_id AS refId,
|
|
11
|
+
j.fullkey AS field, j.value AS body FROM task_records, json_tree(brief) j
|
|
12
|
+
WHERE j.type = 'text' AND (j.key IN ('objective','technicalApproach','currentFocus','leaderSummary')
|
|
13
|
+
OR j.path = '$.boundaries')`,
|
|
14
|
+
`SELECT task_id, 'message', 'task-message', message_id, '$.body', json_extract(payload, '$.body') FROM messages`,
|
|
15
|
+
`SELECT task_id, 'decision', 'task-decision', decision_id, j.fullkey, j.value
|
|
16
|
+
FROM decisions, json_each(payload) j WHERE j.key IN ('title','rationale','supersededReason') AND j.type = 'text'`,
|
|
17
|
+
`SELECT task_id, 'result', 'run', turn_id, '$.result.output', json_extract(payload, '$.result.output') FROM turns`,
|
|
18
|
+
`SELECT task_id, 'completion', 'task', task_id, '$.completionSummary',
|
|
19
|
+
json_extract(payload, '$.completionSummary') FROM task_records`
|
|
20
|
+
];
|
|
21
|
+
const params = {
|
|
22
|
+
query: input.query, taskId: input.taskId ?? null, kind: input.kind ?? null, limit: input.limit,
|
|
23
|
+
afterTask: input.after?.taskId ?? "", afterKind: input.after?.kind ?? "",
|
|
24
|
+
afterRef: input.after?.refId ?? "", afterField: input.after?.field ?? ""
|
|
25
|
+
};
|
|
26
|
+
// SQLite lower() folds ASCII only; other Unicode is matched literally.
|
|
27
|
+
// Positions and substr use Unicode code points, not UTF-16 units or bytes.
|
|
28
|
+
return db.prepare(`WITH sources AS (${branches.join(" UNION ALL ")}),
|
|
29
|
+
matches AS (SELECT *, instr(lower(body), lower(@query)) AS hit FROM sources
|
|
30
|
+
WHERE (@taskId IS NULL OR taskId = @taskId) AND (@kind IS NULL OR kind = @kind)
|
|
31
|
+
AND (taskId, kind, refId, field) > (@afterTask, @afterKind, @afterRef, @afterField))
|
|
32
|
+
SELECT taskId, kind, store, refId, field, max(0, hit - 81) AS offset,
|
|
33
|
+
substr(body, max(1, hit - 80), 320) AS snippet
|
|
34
|
+
FROM matches WHERE hit > 0 ORDER BY taskId, kind, refId, field LIMIT @limit`).all(params);
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { knowledgeProposalFingerprint, validateProject } from "../../repository/project.js";
|
|
3
|
+
/**
|
|
4
|
+
* The declared 1.3 -> 1.4 payload transition. Never used by ordinary readers.
|
|
5
|
+
* Version 1 is the observed pre-migration head, not a claim that no earlier
|
|
6
|
+
* edits happened. Existing proposals and frozen Context snapshots retain their
|
|
7
|
+
* original identities, decisions and evidence; snapshots are never rewritten.
|
|
8
|
+
*/
|
|
9
|
+
export function migrateProjectKnowledge(db) {
|
|
10
|
+
const rows = db.prepare("SELECT id, payload FROM projects ORDER BY id").all();
|
|
11
|
+
for (const row of rows) {
|
|
12
|
+
const project = JSON.parse(row.payload);
|
|
13
|
+
if (project.id !== row.id || !Array.isArray(project.knowledge) || !Array.isArray(project.knowledgeProposals)) {
|
|
14
|
+
throw new Error(`Invalid 1.3 Project knowledge container: ${row.id}.`);
|
|
15
|
+
}
|
|
16
|
+
const knowledgeProposals = project.knowledgeProposals.map(proposal => {
|
|
17
|
+
if (proposal.schemaVersion !== 1)
|
|
18
|
+
throw new Error(`Invalid 1.3 Knowledge proposal: ${proposal.id}.`);
|
|
19
|
+
const sourceKey = [proposal.projectId, proposal.source.taskId, proposal.source.decisionId ?? "",
|
|
20
|
+
proposal.source.milestoneId ?? "", proposal.source.commitSha ?? ""].join("|");
|
|
21
|
+
const previousFingerprint = createHash("sha256")
|
|
22
|
+
.update(`${sourceKey}\u0000${proposal.title}\u0000${proposal.body}`).digest("hex");
|
|
23
|
+
if (proposal.fingerprint !== previousFingerprint) {
|
|
24
|
+
throw new Error(`Invalid 1.3 Knowledge fingerprint: ${proposal.id}.`);
|
|
25
|
+
}
|
|
26
|
+
return { ...proposal, schemaVersion: 2, fingerprint: knowledgeProposalFingerprint(proposal) };
|
|
27
|
+
});
|
|
28
|
+
const knowledge = project.knowledge.map(entry => {
|
|
29
|
+
if (entry.schemaVersion !== 1 || "version" in entry || "history" in entry
|
|
30
|
+
|| "scope" in entry || "expiresWhen" in entry) {
|
|
31
|
+
throw new Error(`Invalid 1.3 Project knowledge: ${entry.id}.`);
|
|
32
|
+
}
|
|
33
|
+
const proposal = knowledgeProposals.find(candidate => candidate.id === entry.provenance?.proposalId);
|
|
34
|
+
const backed = proposal !== undefined && proposal.status === "accepted"
|
|
35
|
+
&& proposal.knowledgeId === entry.id && proposal.title === entry.title && proposal.body === entry.body
|
|
36
|
+
&& proposal.source.taskId === entry.provenance?.taskId
|
|
37
|
+
&& proposal.source.decisionId === entry.provenance?.decisionId
|
|
38
|
+
&& proposal.source.milestoneId === entry.provenance?.milestoneId
|
|
39
|
+
&& proposal.source.commitSha === entry.provenance?.commitSha;
|
|
40
|
+
return {
|
|
41
|
+
...entry, schemaVersion: 2, version: 1, history: [],
|
|
42
|
+
...(backed ? {
|
|
43
|
+
...(proposal.scope === undefined ? {} : { scope: proposal.scope }),
|
|
44
|
+
...(proposal.expiresWhen === undefined ? {} : { expiresWhen: proposal.expiresWhen }),
|
|
45
|
+
provenance: { ...entry.provenance, fingerprint: proposal.fingerprint }
|
|
46
|
+
} : {})
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
const migrated = validateProject({ ...project, knowledge, knowledgeProposals });
|
|
50
|
+
db.prepare("UPDATE projects SET payload=? WHERE id=?").run(JSON.stringify(migrated), row.id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -30,7 +30,8 @@ export function archiveRetainedResources(store, task) {
|
|
|
30
30
|
resources.push({ resource: `run:${task.id}/${run.id}`,
|
|
31
31
|
detail: `AgentRun remains active (${run.roleName}); archive is not stop evidence.` });
|
|
32
32
|
}
|
|
33
|
-
for (const job of store.listDurableJobs(task.id).filter(j => ["queued", "running"
|
|
33
|
+
for (const job of store.listDurableJobs(task.id).filter(j => ["queued", "running"].includes(j.status)
|
|
34
|
+
|| (j.status === "unknown-needs-attention" && j.acknowledgedAt === undefined))) {
|
|
34
35
|
resources.push({ resource: `job:${task.id}/${job.id}`, detail: `DurableJob remains ${job.status}.` });
|
|
35
36
|
}
|
|
36
37
|
for (const attempt of store.listIntegrationAttempts(task.id).filter(a => ["running", "blocked", "conflicted", "validating"].includes(a.status))) {
|
|
@@ -110,20 +111,37 @@ export function taskArchiveDiagnostics(store, task) {
|
|
|
110
111
|
...(event.payload.paths === undefined ? {} : { paths: JSON.parse(event.payload.paths) }) });
|
|
111
112
|
}
|
|
112
113
|
const forced = archive?.payload.force === "true";
|
|
113
|
-
const
|
|
114
|
-
|
|
114
|
+
const last = cleanup.at(-1);
|
|
115
|
+
const cleanupFinished = last?.payload.status === "finished"
|
|
116
|
+
|| (cleanup.length === 0 && archive !== undefined && archive.payload.cleanup !== "pending" && !forced);
|
|
117
|
+
if (task.status === "archived" && !cleanupFinished)
|
|
115
118
|
warnings.push({ resource: `task:${task.id}`,
|
|
116
119
|
detail: "Archive committed; cleanup has not finished. Retained references require explicit inspection; archive retry does not replay cleanup." });
|
|
120
|
+
const retainedResources = archiveRetainedResources(store, task);
|
|
121
|
+
const passStart = cleanup.map(e => e.payload.resource === `cleanup-pass:${task.id}` && e.payload.status === "started").lastIndexOf(true);
|
|
122
|
+
const lastPass = cleanup.slice(passStart >= 0 ? passStart
|
|
123
|
+
: cleanup.map(e => e.payload.status).lastIndexOf("finished", cleanup.length - 2) + 1);
|
|
124
|
+
// Derived from current references AND a completed physical cleanup attempt;
|
|
125
|
+
// neither archive nor an empty workspace list alone establishes release.
|
|
126
|
+
const allResourcesReleased = task.status === "archived" && cleanupFinished
|
|
127
|
+
&& retainedResources.length === 0
|
|
128
|
+
&& lastPass.some(e => e.payload.resource === `runtime:${task.id}` && e.payload.status === "released")
|
|
129
|
+
&& !lastPass.some(e => e.payload.status === "retained");
|
|
117
130
|
return { archived: task.status === "archived", forced,
|
|
118
131
|
disposition: archive?.payload.workspaceDisposition ?? null,
|
|
119
|
-
cleanupFinished,
|
|
120
|
-
|
|
132
|
+
cleanupFinished, allResourcesReleased,
|
|
133
|
+
warnings: warnings.slice(-32), retainedResources: retainedResources.slice(0, 32),
|
|
134
|
+
counts: { warnings: warnings.length, retainedResources: retainedResources.length, cleanupEvents: cleanup.length },
|
|
135
|
+
cleanupEvents: cleanup.slice(-32),
|
|
136
|
+
actions: [`yui task archive-preflight ${task.id} --${archive?.payload.workspaceDisposition === "abandoned" ? "abandon" : "integrated"}`,
|
|
137
|
+
`yui task archive-cleanup ${task.id}`, `yui task event list ${task.id}`] };
|
|
121
138
|
}
|
|
122
139
|
export function renderArchiveDiagnostics(data) {
|
|
123
140
|
return [
|
|
124
|
-
`Archived: ${data.archived}; forced: ${data.forced}; cleanup finished: ${data.cleanupFinished}`,
|
|
125
|
-
`Retained references: ${data.retainedResources
|
|
141
|
+
`Archived: ${data.archived}; forced: ${data.forced}; cleanup finished: ${data.cleanupFinished}; all resources released: ${data.allResourcesReleased}`,
|
|
142
|
+
`Retained references: ${data.counts.retainedResources}; a finished cleanup attempt is not physical resource release evidence.`,
|
|
126
143
|
...data.warnings.map(w => `Warning [${w.resource}]: ${w.detail}`),
|
|
127
|
-
...data.retainedResources.map(r => `Retained [${r.resource}]: ${r.detail}${r.paths ? ` (${r.paths.join(", ")})` : ""}`)
|
|
144
|
+
...data.retainedResources.map(r => `Retained [${r.resource}]: ${r.detail}${r.paths ? ` (${r.paths.join(", ")})` : ""}`),
|
|
145
|
+
`Inspect/retry: ${data.actions.join("; ")}`
|
|
128
146
|
].join("\n") + "\n";
|
|
129
147
|
}
|
|
@@ -3,6 +3,7 @@ import { WorkspaceCleanupBlockedError } from "../repository/taskWorkspacePrepare
|
|
|
3
3
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
4
4
|
import { hasRuntimeLifecycleWork, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
5
5
|
import { cleanupCheckFromError, renderCleanupCheck } from "../workspace/cleanupInspection.js";
|
|
6
|
+
import { jobCleanupBlocker } from "../job/jobCleanupInspection.js";
|
|
6
7
|
/** Semantic admission facts. Cleanup still has its own resource boundary. */
|
|
7
8
|
export function archiveSettlementChecks(store, task) {
|
|
8
9
|
const checks = [];
|
|
@@ -13,6 +14,9 @@ export function archiveSettlementChecks(store, task) {
|
|
|
13
14
|
for (const item of store.listWorkItems(task.id).filter(i => i.status === "open")) {
|
|
14
15
|
add(`work-item:${task.id}/${item.id}`, "owner-unsettled", "WorkItem must be accepted or explicitly retired before archive.", ["accepted", "retired"], item.status, `yui task work show ${task.id}/${item.id}`);
|
|
15
16
|
}
|
|
17
|
+
for (const round of store.listReviewRounds(task.id).filter(r => ["pending", "running"].includes(r.status))) {
|
|
18
|
+
add(`review-round:${task.id}/${round.id}`, "owner-unsettled", "ReviewRound must be terminal before ordinary archive.", ["completed", "failed"], round.status, `yui task context list ${task.id} --store review-round`);
|
|
19
|
+
}
|
|
16
20
|
for (const attempt of store.listIntegrationAttempts(task.id).filter(a => ["running", "blocked", "conflicted", "validating"].includes(a.status))) {
|
|
17
21
|
add(`integration-attempt:${task.id}/${attempt.id}`, "unresolved-integration", "Task has an unresolved Integration Attempt.", "terminal attempt", attempt.status, `yui task integration show ${task.id}/${attempt.id}`);
|
|
18
22
|
}
|
|
@@ -24,15 +28,17 @@ export function archiveSettlementChecks(store, task) {
|
|
|
24
28
|
/** Unknown execution is never permission to remove workspaces, even after
|
|
25
29
|
* force admission. These facts are reloaded in the actual force cleanup path.
|
|
26
30
|
*/
|
|
27
|
-
export function archiveExecutionChecks(store, taskId) {
|
|
31
|
+
export function archiveExecutionChecks(store, taskId, home, phase = "before-removal") {
|
|
28
32
|
const checks = [];
|
|
29
33
|
const add = (resource, reason, detail, observed, action) => checks.push({ resource, reason, status: "unknown", detail, expected: "settled execution",
|
|
30
34
|
observed, sources: [resource], actions: [action] });
|
|
31
35
|
for (const run of store.listRuns(taskId).filter(r => r.status === "active")) {
|
|
32
36
|
add(`run:${taskId}/${run.id}`, "active-turn", "AgentRun remains active; archive is not stop evidence.", run.status, `yui task run show ${taskId}/${run.id}`);
|
|
33
37
|
}
|
|
34
|
-
for (const job of store.listDurableJobs(taskId)
|
|
35
|
-
|
|
38
|
+
for (const job of store.listDurableJobs(taskId)) {
|
|
39
|
+
const blocker = jobCleanupBlocker(job, home, phase);
|
|
40
|
+
if (blocker !== undefined)
|
|
41
|
+
add(`job:${taskId}/${job.id}`, "unresolved-execution", blocker, job.status, `yui job get --task ${taskId} --job ${job.id}`);
|
|
36
42
|
}
|
|
37
43
|
for (const role of store.listRoles(taskId)) {
|
|
38
44
|
const resource = `role:${taskId}/${role.name}`;
|
|
@@ -65,7 +71,7 @@ export async function inspectTaskArchive(coordinator, request) {
|
|
|
65
71
|
observed: { coverage: p.coverage, merged: p.merged, verified: p.verified, localCommit: p.publication?.localCommit ?? null },
|
|
66
72
|
sources: [`task:${task.id}`, ...(p.publication === null ? [] : [`publication:${task.id}/${p.publication.id}`])],
|
|
67
73
|
actions: [`yui task remote-delivery ${task.id}`] }));
|
|
68
|
-
const execution = archiveExecutionChecks(store, task.id);
|
|
74
|
+
const execution = archiveExecutionChecks(store, task.id, preparer.home);
|
|
69
75
|
const runtimeResource = `runtime:${task.id}`;
|
|
70
76
|
if (runtime.assertTaskPhysicalResourcesReleased === undefined) {
|
|
71
77
|
execution.push(...cleanupCheckFromError(null, runtimeResource, [`task:${task.id}`], [`yui task role status ${task.id}`]));
|
|
@@ -100,25 +106,36 @@ export async function inspectTaskArchive(coordinator, request) {
|
|
|
100
106
|
checks });
|
|
101
107
|
}
|
|
102
108
|
const eligible = ["completed", "cancelled", "archived"].includes(task.status);
|
|
109
|
+
const limit = 32;
|
|
110
|
+
const boundedExecution = execution.slice(0, limit);
|
|
103
111
|
return { taskId: task.id, observedAt: new Date().toISOString(), disposition: request.disposition, force: request.force,
|
|
104
112
|
readOnly: true, authorizesCleanup: false,
|
|
105
113
|
archive: { status: task.status, eligible, alreadyArchived: task.status === "archived",
|
|
106
|
-
settlement, delivery: deliveryChecks, forceBypassesSettlement: request.force,
|
|
114
|
+
settlement: settlement.slice(0, limit), delivery: deliveryChecks.slice(0, limit), forceBypassesSettlement: request.force,
|
|
115
|
+
execution: boundedExecution, physicalRetentionBlocksArchive: false,
|
|
107
116
|
requiresIndependentAuthorization: true },
|
|
108
|
-
cleanup: { execution, resources
|
|
109
|
-
|
|
117
|
+
cleanup: { execution: boundedExecution, resources: resources.slice(0, limit).map(resource => ({
|
|
118
|
+
...resource, checkCount: resource.checks.length, checks: resource.checks.slice(0, limit)
|
|
119
|
+
})) },
|
|
120
|
+
counts: { settlement: settlement.length, delivery: deliveryChecks.length,
|
|
121
|
+
execution: execution.length, resources: resources.length },
|
|
122
|
+
actions: [`yui task context list ${task.id} --store managed-workspace`,
|
|
123
|
+
`yui task remote-delivery ${task.id}`, `yui task event list ${task.id}`],
|
|
124
|
+
note: "Current observations only. Ordinary archive requires settled business, exact delivery and stopped execution; safe physical retention is separate. Cleanup reloads these checks after archive commits. A finished attempt does not prove resource release." };
|
|
110
125
|
}
|
|
111
126
|
export function renderTaskArchivePreflight(data) {
|
|
112
127
|
return [
|
|
113
128
|
`Archive preflight: ${data.taskId} (${data.disposition}${data.force ? ", force" : ""}); read-only, not authorization`,
|
|
114
129
|
`Task: ${data.archive.status}; terminal eligibility: ${data.archive.eligible}`,
|
|
130
|
+
`Checks/resources (showing at most 32 per list): settlement=${data.counts.settlement}; delivery=${data.counts.delivery}; execution=${data.counts.execution}; resources=${data.counts.resources}`,
|
|
115
131
|
...data.archive.settlement.map(c => `Admission${data.force ? " (force preserves)" : ""}: ${renderCleanupCheck(c)}`),
|
|
116
132
|
...data.archive.delivery.map(c => `Delivery${data.force || data.disposition === "abandoned" ? " (advisory for admission)" : ""}: ${renderCleanupCheck(c)}`),
|
|
117
133
|
...data.cleanup.execution.map(c => `Cleanup: ${renderCleanupCheck(c)}`),
|
|
118
134
|
...data.cleanup.resources.flatMap(r => [
|
|
119
|
-
`Workspace [${r.resource}]: ${r.status}`,
|
|
135
|
+
`Workspace [${r.resource}]: ${r.status}; checks=${r.checkCount}`,
|
|
120
136
|
...r.checks.map(c => ` ${renderCleanupCheck(c)}`)
|
|
121
137
|
]),
|
|
122
|
-
data.note
|
|
138
|
+
data.note,
|
|
139
|
+
`Inspect full evidence: ${data.actions.join("; ")}`
|
|
123
140
|
].join("\n") + "\n";
|
|
124
141
|
}
|
|
@@ -5,6 +5,7 @@ import { archiveExecutionChecks, archiveSettlementChecks } from "./archivePrefli
|
|
|
5
5
|
import { admitLeaderArchive, assertLeaderArchiveAdmission } from "./leaderArchiveAuthority.js";
|
|
6
6
|
import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./remoteDeliveryService.js";
|
|
7
7
|
import { selectNewTaskRoleSession } from "../executor/agentExecutor.js";
|
|
8
|
+
import { taskArchiveDiagnostics } from "./archiveDiagnostics.js";
|
|
8
9
|
/** A single foreground Controller operation survives stopping its requesting
|
|
9
10
|
* Session. It has no retry worker: uncertain receipts are inspected, not
|
|
10
11
|
* replayed. All existing settlement, delivery and cleanup checks remain. */
|
|
@@ -28,7 +29,7 @@ export async function archiveLeaderTask(store, coordinator, taskId, environment,
|
|
|
28
29
|
// Only the requesting no-Run Leader may be stopped as part of this action.
|
|
29
30
|
// Another Run, Job, unknown input, or then handoff is not archive authority.
|
|
30
31
|
const ownResources = `role:${taskId}/leader`;
|
|
31
|
-
const execution = archiveExecutionChecks(store, taskId).filter(check => !(check.resource === ownResources
|
|
32
|
+
const execution = archiveExecutionChecks(store, taskId, coordinator.preparer.home, "before-role-stop").filter(check => !(check.resource === ownResources
|
|
32
33
|
&& ownInput?.runId === undefined && ownInput?.status === "accepted"
|
|
33
34
|
&& ["unresolved-provider-input", "unresolved-mailbox"].includes(check.reason)));
|
|
34
35
|
if (execution.length)
|
|
@@ -39,7 +40,7 @@ export async function archiveLeaderTask(store, coordinator, taskId, environment,
|
|
|
39
40
|
}
|
|
40
41
|
let candidate = null;
|
|
41
42
|
const workspace = store.getTaskWorkspace(taskId);
|
|
42
|
-
if (task.projectBindings.length && workspace) {
|
|
43
|
+
if (task.status === "cancelled" && task.projectBindings.length && workspace) {
|
|
43
44
|
const snapshot = await coordinator.preparer.snapshotDirectTaskMain(workspace, task.projectBindings.map(binding => binding.projectId));
|
|
44
45
|
candidate = { schemaVersion: 1, projects: snapshot.projects.map(project => ({
|
|
45
46
|
projectId: project.projectId, commit: project.headCommit
|
|
@@ -47,14 +48,6 @@ export async function archiveLeaderTask(store, coordinator, taskId, environment,
|
|
|
47
48
|
}
|
|
48
49
|
const proof = createTaskRemoteDeliveryProof(store, task, candidate);
|
|
49
50
|
assertTaskRemoteDeliveryIntegrated(proof.delivery);
|
|
50
|
-
for (const owned of store.listManagedWorkspaces(taskId)) {
|
|
51
|
-
const disposition = owned.owner.type === "work-item"
|
|
52
|
-
&& store.getWorkItem(taskId, owned.owner.workItemId)?.status === "retired" ? "abandoned" : "integrated";
|
|
53
|
-
const checks = await coordinator.preparer.inspectWorkspaceCleanup(owned, disposition);
|
|
54
|
-
if (checks.some(check => !(owned.owner.type === "task" && check.reason === "git-worktree-registrations"))) {
|
|
55
|
-
throw new Error(`Archive workspace is not clean/removable: ${JSON.stringify(checks)}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
51
|
// Reauthenticate after asynchronous inspection, before the first effect.
|
|
59
52
|
admitLeaderArchive(store, taskId, environment, request.sourceMessage, request.purpose);
|
|
60
53
|
assertLeaderArchiveAdmission(store, taskId, admission);
|
|
@@ -94,14 +87,15 @@ export async function archiveLeaderTask(store, coordinator, taskId, environment,
|
|
|
94
87
|
// the first verified stop already released its physical owner records.
|
|
95
88
|
tx.saveTaskRoleSessionSet(selectNewTaskRoleSession(stopped, stopped.activeAgentId, new Date()));
|
|
96
89
|
});
|
|
97
|
-
|
|
98
|
-
if (cleanup.status !== "removed")
|
|
99
|
-
throw new Error(cleanup.error ?? `Archive cleanup ${cleanup.status}.`);
|
|
90
|
+
await coordinator.prepareTaskForArchive(taskId);
|
|
100
91
|
runTaskCommand(["archive", taskId, "--integrated"], store, {
|
|
101
|
-
environment,
|
|
92
|
+
environment, yuiHome: coordinator.preparer.home,
|
|
93
|
+
archiveLeaderAdmission: admission, archiveRemoteDeliveryProof: proof
|
|
102
94
|
});
|
|
95
|
+
await coordinator.cleanupArchivedTask(taskId, "integrated");
|
|
103
96
|
record("task.leader-archive-result", { status: "archived" });
|
|
104
|
-
return { taskId, requestId: request.requestId, status: "archived"
|
|
97
|
+
return { taskId, requestId: request.requestId, status: "archived",
|
|
98
|
+
...taskArchiveDiagnostics(store, store.getTask(taskId)) };
|
|
105
99
|
}
|
|
106
100
|
catch (error) {
|
|
107
101
|
record("task.leader-archive-result", { status: store.getTask(taskId)?.status === "archived" ? "archived" : "failed",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { runTaskCommand, validateTaskArchiveRequest } from "../commands/taskCommands.js";
|
|
2
|
+
import { taskArchiveDiagnostics } from "./archiveDiagnostics.js";
|
|
3
|
+
import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./remoteDeliveryService.js";
|
|
4
|
+
/** Read-only admission shared by CLI and Web. Cancelled Project Tasks need a
|
|
5
|
+
* workspace snapshot before delivery can be proved; that later preparation
|
|
6
|
+
* may have effects and must not be reported as a known non-submission. */
|
|
7
|
+
export function validateOrdinaryTaskArchive(store, taskId, options) {
|
|
8
|
+
validateTaskArchiveRequest([taskId, "--integrated"], store, options);
|
|
9
|
+
const task = store.getTask(taskId);
|
|
10
|
+
if (task.status !== "archived" && (task.status === "completed" || !task.projectBindings.length)) {
|
|
11
|
+
assertTaskRemoteDeliveryIntegrated(createTaskRemoteDeliveryProof(store, task).delivery);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** The local user's ordinary archive path. No force, abandonment, source
|
|
15
|
+
* impersonation, or private cleanup policy is accepted by this port. */
|
|
16
|
+
export async function archiveOrdinaryTask(coordinator, taskId, options) {
|
|
17
|
+
const { store, preparer } = coordinator;
|
|
18
|
+
const args = [taskId, "--integrated"];
|
|
19
|
+
validateOrdinaryTaskArchive(store, taskId, options);
|
|
20
|
+
const task = store.getTask(taskId);
|
|
21
|
+
if (task.status !== "archived") {
|
|
22
|
+
let candidate = null;
|
|
23
|
+
if (task.status === "cancelled" && task.projectBindings.length) {
|
|
24
|
+
await preparer.prepareTaskWorkspace(taskId);
|
|
25
|
+
const workspace = store.getTaskWorkspace(taskId);
|
|
26
|
+
if (!workspace || workspace.owner.type !== "task" || workspace.owner.taskId !== taskId) {
|
|
27
|
+
throw new Error(`Task has no authoritative main workspace: ${taskId}.`);
|
|
28
|
+
}
|
|
29
|
+
const snapshot = await preparer.snapshotDirectTaskMain(workspace, task.projectBindings.map(binding => binding.projectId));
|
|
30
|
+
candidate = { schemaVersion: 1, projects: snapshot.projects.map(project => ({
|
|
31
|
+
projectId: project.projectId, commit: project.headCommit
|
|
32
|
+
})) };
|
|
33
|
+
}
|
|
34
|
+
const proof = createTaskRemoteDeliveryProof(store, task, candidate);
|
|
35
|
+
assertTaskRemoteDeliveryIntegrated(proof.delivery);
|
|
36
|
+
await coordinator.prepareTaskForArchive(taskId);
|
|
37
|
+
runTaskCommand(["archive", ...args], store, { ...options, archiveRemoteDeliveryProof: proof });
|
|
38
|
+
await coordinator.cleanupArchivedTask(taskId, "integrated");
|
|
39
|
+
}
|
|
40
|
+
const current = store.getTask(taskId);
|
|
41
|
+
return { task: current, ...taskArchiveDiagnostics(store, current) };
|
|
42
|
+
}
|
package/dist/tmux/tmuxManager.js
CHANGED
|
@@ -263,16 +263,16 @@ export class TmuxManager {
|
|
|
263
263
|
// with terminal output that predates this attach.
|
|
264
264
|
this.run(["set-option", "-t", clientSession, "status", "off"]);
|
|
265
265
|
this.run(["set-option", "-t", clientSession, "mouse", "on"]);
|
|
266
|
+
// Arm automatic release only after a client has attached. Enabling it
|
|
267
|
+
// on the initially detached session would destroy it before attach.
|
|
268
|
+
// A client-detached hook is unsafe: after explicit session destruction,
|
|
269
|
+
// tmux can resolve that notification in a surviving session's context
|
|
270
|
+
// and execute its hook, cascading into unrelated viewer/writer leases.
|
|
266
271
|
this.run([
|
|
267
|
-
"set-hook", "-t", clientSession, "client-
|
|
268
|
-
`
|
|
272
|
+
"set-hook", "-t", clientSession, "client-attached",
|
|
273
|
+
`set-option -t ${clientSession} destroy-unattached on`
|
|
274
|
+
+ (attachedNotice === undefined ? "" : ` ; display-message -d 10000 ${tmuxWord(attachedNotice)}`)
|
|
269
275
|
]);
|
|
270
|
-
if (attachedNotice !== undefined) {
|
|
271
|
-
this.run([
|
|
272
|
-
"set-hook", "-t", clientSession, "client-attached",
|
|
273
|
-
`display-message -d 10000 ${tmuxWord(attachedNotice)}`
|
|
274
|
-
]);
|
|
275
|
-
}
|
|
276
276
|
return clientSession;
|
|
277
277
|
}
|
|
278
278
|
catch (error) {
|
|
@@ -1,63 +1,23 @@
|
|
|
1
|
+
// Every asset the dashboard serves, keyed by URL path. Stylesheets come from
|
|
2
|
+
// the ordered styles registry, client modules from the client registry, and
|
|
3
|
+
// fonts and xterm from their packages; the shell links them in this order.
|
|
1
4
|
import { readFileSync } from "node:fs";
|
|
2
5
|
import { createRequire } from "node:module";
|
|
3
|
-
import {
|
|
4
|
-
import { APP_SCRIPT } from "./client/app.js";
|
|
5
|
-
import { COMPONENTS_SCRIPT } from "./client/components.js";
|
|
6
|
-
import { DETAIL_SCRIPT } from "./client/detail.js";
|
|
7
|
-
import { DOCK_SCRIPT } from "./client/dock.js";
|
|
8
|
-
import { DOM_SCRIPT } from "./client/dom.js";
|
|
9
|
-
import { EVIDENCE_SCRIPT } from "./client/evidence.js";
|
|
10
|
-
import { FORMAT_SCRIPT } from "./client/format.js";
|
|
11
|
-
import { FORMS_SCRIPT } from "./client/forms.js";
|
|
12
|
-
import { I18N_SCRIPT } from "./client/i18n.js";
|
|
13
|
-
import { MARKDOWN_SCRIPT } from "./client/markdown.js";
|
|
14
|
-
import { OVERVIEW_SCRIPT } from "./client/overview.js";
|
|
15
|
-
import { RECORDS_SCRIPT } from "./client/records.js";
|
|
16
|
-
import { SECTIONS_SCRIPT } from "./client/sections.js";
|
|
17
|
-
import { SIDEBAR_SCRIPT } from "./client/sidebar.js";
|
|
18
|
-
import { THEME_SCRIPT } from "./client/theme.js";
|
|
19
|
-
import { DASHBOARD_HTML } from "./shell.js";
|
|
20
|
-
import { FONT_FACE_STYLES } from "./fonts.js";
|
|
6
|
+
import { APP_SCRIPT, CLIENT_MODULES } from "./client/index.js";
|
|
21
7
|
import { FONT_WOFF2_BASE64 } from "./fontData.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { LAYOUT_STYLES } from "./styles/layout.js";
|
|
25
|
-
import { MARKDOWN_STYLES } from "./styles/markdown.js";
|
|
26
|
-
import { RESPONSIVE_STYLES } from "./styles/responsive.js";
|
|
27
|
-
import { TOKEN_STYLES } from "./styles/tokens.js";
|
|
28
|
-
import { VIEW_STYLES } from "./styles/views.js";
|
|
8
|
+
import { DASHBOARD_HTML } from "./shell/index.js";
|
|
9
|
+
import { STYLESHEETS } from "./styles/index.js";
|
|
29
10
|
const require = createRequire(import.meta.url);
|
|
30
11
|
const CSS = "text/css; charset=utf-8";
|
|
31
12
|
const JS = "text/javascript; charset=utf-8";
|
|
32
13
|
export const WEB_ASSETS = Object.freeze({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"/assets/css/base.css": { contentType: CSS, body: BASE_STYLES },
|
|
36
|
-
"/assets/css/layout.css": { contentType: CSS, body: LAYOUT_STYLES },
|
|
37
|
-
"/assets/css/components.css": { contentType: CSS, body: COMPONENT_STYLES },
|
|
38
|
-
"/assets/css/views.css": { contentType: CSS, body: VIEW_STYLES },
|
|
39
|
-
"/assets/css/markdown.css": { contentType: CSS, body: MARKDOWN_STYLES },
|
|
40
|
-
"/assets/css/responsive.css": { contentType: CSS, body: RESPONSIVE_STYLES },
|
|
41
|
-
"/assets/js/dom.js": { contentType: JS, body: DOM_SCRIPT },
|
|
42
|
-
"/assets/js/format.js": { contentType: JS, body: FORMAT_SCRIPT },
|
|
43
|
-
"/assets/js/i18n.js": { contentType: JS, body: I18N_SCRIPT },
|
|
44
|
-
"/assets/js/markdown.js": { contentType: JS, body: MARKDOWN_SCRIPT },
|
|
45
|
-
"/assets/js/theme.js": { contentType: JS, body: THEME_SCRIPT },
|
|
46
|
-
"/assets/js/api.js": { contentType: JS, body: API_SCRIPT },
|
|
47
|
-
"/assets/js/components.js": { contentType: JS, body: COMPONENTS_SCRIPT },
|
|
48
|
-
"/assets/js/records.js": { contentType: JS, body: RECORDS_SCRIPT },
|
|
49
|
-
"/assets/js/forms.js": { contentType: JS, body: FORMS_SCRIPT },
|
|
50
|
-
"/assets/js/evidence.js": { contentType: JS, body: EVIDENCE_SCRIPT },
|
|
51
|
-
"/assets/js/sidebar.js": { contentType: JS, body: SIDEBAR_SCRIPT },
|
|
52
|
-
"/assets/js/overview.js": { contentType: JS, body: OVERVIEW_SCRIPT },
|
|
53
|
-
"/assets/js/sections.js": { contentType: JS, body: SECTIONS_SCRIPT },
|
|
54
|
-
"/assets/js/detail.js": { contentType: JS, body: DETAIL_SCRIPT },
|
|
55
|
-
"/assets/js/dock.js": { contentType: JS, body: DOCK_SCRIPT },
|
|
14
|
+
...Object.fromEntries(STYLESHEETS.map(({ name, body }) => [`/assets/css/${name}.css`, { contentType: CSS, body }])),
|
|
15
|
+
...Object.fromEntries(Object.entries(CLIENT_MODULES).map(([path, body]) => [`/assets/js/${path}.js`, { contentType: JS, body }])),
|
|
56
16
|
"/assets/app.js": { contentType: JS, body: APP_SCRIPT },
|
|
57
17
|
...fontAssets(),
|
|
58
|
-
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs",
|
|
59
|
-
"/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs",
|
|
60
|
-
"/assets/vendor/xterm.css": vendorAsset("@xterm/xterm/css/xterm.css",
|
|
18
|
+
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", JS),
|
|
19
|
+
"/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs", JS),
|
|
20
|
+
"/assets/vendor/xterm.css": vendorAsset("@xterm/xterm/css/xterm.css", CSS)
|
|
61
21
|
});
|
|
62
22
|
export { DASHBOARD_HTML };
|
|
63
23
|
export function findWebAsset(pathname) {
|