@zq-silk/yui 2.3.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.
Files changed (183) hide show
  1. package/README.md +15 -1
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +17 -8
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +25 -2
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +10 -1
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  23. package/dist/controller/jobControl.js +2 -0
  24. package/dist/controller/nativeHostInput.js +37 -0
  25. package/dist/controller/runtime.js +93 -2
  26. package/dist/controller/runtimeEventInbox.js +2 -2
  27. package/dist/controller/runtimeEventProcessor.js +3 -3
  28. package/dist/doctor/doctor.js +10 -0
  29. package/dist/execution/reviewMainRun.js +1 -0
  30. package/dist/execution/workItemMainRun.js +1 -0
  31. package/dist/executor/codexConfigConflict.js +6 -4
  32. package/dist/executor/effectiveLaunch.js +24 -1
  33. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  34. package/dist/integration/gitIntegrationService.js +7 -12
  35. package/dist/integration/integrationSourceApplication.js +4 -2
  36. package/dist/job/httpPreview.js +61 -0
  37. package/dist/job/jobRunner.js +12 -2
  38. package/dist/kernel/builtinCapabilities.js +3 -1
  39. package/dist/message/message.js +15 -1
  40. package/dist/output/rolePresentation.js +4 -1
  41. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  42. package/dist/repository/gitCompatibility.js +62 -0
  43. package/dist/repository/gitWorkspace.js +48 -71
  44. package/dist/repository/gitWorktreeInventory.js +121 -0
  45. package/dist/repository/project.js +74 -25
  46. package/dist/resources/resourceDiscovery.js +2 -2
  47. package/dist/resources/resourceGc.js +7 -4
  48. package/dist/resources/resourceRegistrar.js +2 -1
  49. package/dist/runtime/agentEndpoint.js +8 -0
  50. package/dist/runtime/agentHost.js +170 -3
  51. package/dist/runtime/codexAppServerRuntime.js +1 -1
  52. package/dist/runtime/codexNativeAccess.js +175 -0
  53. package/dist/runtime/controlledCodexTui.js +116 -0
  54. package/dist/runtime/publicReply.js +23 -0
  55. package/dist/runtime/runtimeObservation.js +48 -0
  56. package/dist/runtime/structuredProviderHost.js +16 -7
  57. package/dist/runtime/taskCostMetrics.js +133 -0
  58. package/dist/runtime/taskUsageMetrics.js +11 -2
  59. package/dist/runtime/taskUsageQuery.js +38 -0
  60. package/dist/scheduler/taskExecutionProjection.js +2 -0
  61. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  62. package/dist/storage/homeLayout.js +13 -0
  63. package/dist/storage/sqliteSchema.js +44 -0
  64. package/dist/storage/sqliteStore.js +53 -0
  65. package/dist/storage/storageVersions.js +1 -1
  66. package/dist/storage/storeRpc.js +2 -0
  67. package/dist/storage/taskSearch.js +35 -0
  68. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  69. package/dist/task/ordinaryArchive.js +42 -0
  70. package/dist/tmux/tmuxManager.js +8 -8
  71. package/dist/web/assets/assetManifest.js +11 -51
  72. package/dist/web/assets/client/app/catalog.js +215 -0
  73. package/dist/web/assets/client/app/main.js +148 -0
  74. package/dist/web/assets/client/app/selection.js +139 -0
  75. package/dist/web/assets/client/app/settings.js +326 -0
  76. package/dist/web/assets/client/app/taskView.js +280 -0
  77. package/dist/web/assets/client/domain/context.js +96 -0
  78. package/dist/web/assets/client/domain/records.js +126 -0
  79. package/dist/web/assets/client/domain/runs.js +119 -0
  80. package/dist/web/assets/client/domain/taskForms.js +197 -0
  81. package/dist/web/assets/client/domain/vocab.js +69 -0
  82. package/dist/web/assets/client/domain/work.js +116 -0
  83. package/dist/web/assets/client/i18n/en.js +900 -0
  84. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  85. package/dist/web/assets/client/index.js +114 -0
  86. package/dist/web/assets/client/layout/resizer.js +37 -0
  87. package/dist/web/assets/client/layout/router.js +35 -0
  88. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  89. package/dist/web/assets/client/layout/sizing.js +95 -0
  90. package/dist/web/assets/client/layout/workspace.js +247 -0
  91. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  92. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  93. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  94. package/dist/web/assets/client/lib/i18n.js +54 -0
  95. package/dist/web/assets/client/lib/markdown.js +167 -0
  96. package/dist/web/assets/client/lib/prefs.js +28 -0
  97. package/dist/web/assets/client/lib/theme.js +68 -0
  98. package/dist/web/assets/client/ui/containers.js +101 -0
  99. package/dist/web/assets/client/ui/controls.js +51 -0
  100. package/dist/web/assets/client/ui/forms.js +48 -0
  101. package/dist/web/assets/client/ui/metrics.js +137 -0
  102. package/dist/web/assets/client/ui/primitives.js +92 -0
  103. package/dist/web/assets/client/ui/text.js +33 -0
  104. package/dist/web/assets/client/ui/toast.js +12 -0
  105. package/dist/web/assets/client/views/bodySearch.js +172 -0
  106. package/dist/web/assets/client/views/createTask.js +85 -0
  107. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  108. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  109. package/dist/web/assets/client/views/dock/preview.js +129 -0
  110. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  111. package/dist/web/assets/client/views/globalInput.js +67 -0
  112. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  113. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  114. package/dist/web/assets/client/views/skills.js +152 -0
  115. package/dist/web/assets/client/views/task/actions.js +36 -0
  116. package/dist/web/assets/client/views/task/delivery.js +185 -0
  117. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  118. package/dist/web/assets/client/views/task/files.js +272 -0
  119. package/dist/web/assets/client/views/task/observation.js +153 -0
  120. package/dist/web/assets/client/views/task/overview.js +161 -0
  121. package/dist/web/assets/client/views/task/page.js +131 -0
  122. package/dist/web/assets/client/views/task/records.js +163 -0
  123. package/dist/web/assets/client/views/task/runtime.js +130 -0
  124. package/dist/web/assets/client/views/task/timeline.js +190 -0
  125. package/dist/web/assets/shared/geometry.js +19 -0
  126. package/dist/web/assets/shared/taskTabs.js +8 -0
  127. package/dist/web/assets/shared/themes.js +93 -0
  128. package/dist/web/assets/shell/dialogs.js +63 -0
  129. package/dist/web/assets/shell/dock.js +40 -0
  130. package/dist/web/assets/shell/index.js +40 -0
  131. package/dist/web/assets/shell/settings.js +34 -0
  132. package/dist/web/assets/shell/sidebar.js +45 -0
  133. package/dist/web/assets/styles/index.js +41 -0
  134. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  135. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  136. package/dist/web/assets/styles/markdown.js +22 -3
  137. package/dist/web/assets/styles/tokens.js +10 -63
  138. package/dist/web/assets/styles/ui/controls.js +76 -0
  139. package/dist/web/assets/styles/ui/forms.js +46 -0
  140. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  141. package/dist/web/assets/styles/views/delivery.js +29 -0
  142. package/dist/web/assets/styles/views/dock.js +71 -0
  143. package/dist/web/assets/styles/views/page.js +32 -0
  144. package/dist/web/assets/styles/views/records.js +70 -0
  145. package/dist/web/assets/styles/views/settings.js +26 -0
  146. package/dist/web/assets/styles/views/sidebar.js +74 -0
  147. package/dist/web/assets/styles/views/task.js +80 -0
  148. package/dist/web/assets/styles/views/terminal.js +38 -0
  149. package/dist/web/taskPreviews.js +205 -0
  150. package/dist/web/tmuxWebTerminal.js +16 -9
  151. package/dist/web/webConversation.js +373 -0
  152. package/dist/web/webMaterials.js +155 -0
  153. package/dist/web/webServer.js +295 -18
  154. package/dist/web/webSettings.js +331 -0
  155. package/dist/web/webSkills.js +86 -0
  156. package/dist/web/webSnapshot.js +40 -8
  157. package/dist/web/webTaskSurface.js +106 -8
  158. package/dist/workspace/gitChangeSetCapture.js +3 -1
  159. package/docs/managed-turn-and-session-runtime.md +9 -0
  160. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  161. package/docs/release-workflow.md +28 -0
  162. package/docs/release-workflow.zh-CN.md +21 -0
  163. package/docs/storage-baseline.md +38 -1
  164. package/docs/storage-baseline.zh-CN.md +22 -0
  165. package/docs/task-discovery.md +28 -0
  166. package/docs/testing/verification-levels.md +2 -2
  167. package/i18n/README.zh-CN.md +1 -1
  168. package/package.json +1 -1
  169. package/dist/web/assets/client/app.js +0 -808
  170. package/dist/web/assets/client/components.js +0 -477
  171. package/dist/web/assets/client/detail.js +0 -379
  172. package/dist/web/assets/client/dock.js +0 -309
  173. package/dist/web/assets/client/evidence.js +0 -364
  174. package/dist/web/assets/client/forms.js +0 -230
  175. package/dist/web/assets/client/i18n.js +0 -1277
  176. package/dist/web/assets/client/markdown.js +0 -114
  177. package/dist/web/assets/client/records.js +0 -198
  178. package/dist/web/assets/client/sections.js +0 -349
  179. package/dist/web/assets/client/theme.js +0 -71
  180. package/dist/web/assets/shell.js +0 -161
  181. package/dist/web/assets/styles/components.js +0 -216
  182. package/dist/web/assets/styles/views.js +0 -309
  183. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -0,0 +1,108 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
+ import { dirname, join, relative, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { verifySkillPackage } from "./skillPackage.js";
6
+ import { BUILTIN_YUI_SKILLS } from "./roleSessionContext.js";
7
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
8
+ const MAX_TEXT = 256 * 1024;
9
+ const digest = (data) => createHash("sha256").update(data).digest("hex");
10
+ /** Read-only source discovery; browsing never snapshots, installs or executes a package. */
11
+ export function skillSource(home, id) {
12
+ if (!SAFE_ID.test(id))
13
+ throw new Error("Invalid Skill id.");
14
+ const builtin = BUILTIN_YUI_SKILLS.has(id);
15
+ return { kind: builtin ? "builtin" : "configured",
16
+ path: builtin ? fileURLToPath(new URL(`../../skills/${id}`, import.meta.url)) : resolve(home, "skills", id) };
17
+ }
18
+ function physical(path) {
19
+ if (realpathSync(path) !== resolve(path))
20
+ throw new Error("Skill paths must not contain symlinks.");
21
+ }
22
+ function textFile(path) {
23
+ physical(path);
24
+ const stat = lstatSync(path);
25
+ if (!stat.isFile() || stat.size > MAX_TEXT)
26
+ throw new Error("Only regular UTF-8 text files up to 256 KiB can be read.");
27
+ const bytes = readFileSync(path);
28
+ if (bytes.length > MAX_TEXT)
29
+ throw new Error("Skill content exceeds 256 KiB.");
30
+ const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
31
+ if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/u.test(content))
32
+ throw new Error("Binary Skill resources cannot be displayed.");
33
+ return { content, byteSize: bytes.length, fileDigest: digest(bytes) };
34
+ }
35
+ export function listSkillSources(home, query = "", cursor = "0") {
36
+ if (query.length > 256 || !/^\d{1,8}$/u.test(cursor))
37
+ throw new Error("Invalid Skill search or cursor.");
38
+ let configured = [];
39
+ try {
40
+ const directory = resolve(home, "skills");
41
+ physical(directory);
42
+ configured = readdirSync(directory).filter(id => SAFE_ID.test(id));
43
+ }
44
+ catch (error) {
45
+ if (error.code !== "ENOENT")
46
+ throw error;
47
+ }
48
+ // Bounded discovery has an explicit limit, never silently presents a partial catalog.
49
+ if (configured.length > 2048)
50
+ throw new Error("Skill directory exceeds the 2048-entry browsing limit.");
51
+ const needle = query.trim().toLowerCase();
52
+ const items = [...new Set([...BUILTIN_YUI_SKILLS, ...configured])].sort().map(id => {
53
+ const source = skillSource(home, id);
54
+ try {
55
+ const file = textFile(join(source.path, "SKILL.md"));
56
+ const description = /^description:\s*(.+)$/mu.exec(file.content)?.[1]
57
+ ?? file.content.split("\n").find(line => line.trim() && !line.startsWith("#")) ?? "";
58
+ return { id, source, description: description.slice(0, 600), error: null };
59
+ }
60
+ catch (error) {
61
+ return { id, source, description: "", error: error instanceof Error ? error.message : String(error) };
62
+ }
63
+ }).filter(item => !needle || `${item.id} ${item.description}`.toLowerCase().includes(needle));
64
+ const offset = Number(cursor);
65
+ return { items: items.slice(offset, offset + 30), total: items.length,
66
+ nextCursor: offset + 30 < items.length ? String(offset + 30) : null };
67
+ }
68
+ export function readSkillSource(home, id, resource = "SKILL.md") {
69
+ const source = skillSource(home, id);
70
+ const files = [];
71
+ let entries = 0;
72
+ function walk(path, depth) {
73
+ if (depth > 32)
74
+ throw new Error("Skill resource depth exceeds 32.");
75
+ physical(path);
76
+ for (const name of readdirSync(path).sort()) {
77
+ if (++entries > 4096)
78
+ throw new Error("Skill exceeds the 4096-entry browsing limit.");
79
+ const child = join(path, name);
80
+ const stat = lstatSync(child);
81
+ if (stat.isSymbolicLink() || !stat.isDirectory() && !stat.isFile())
82
+ throw new Error("Skill contains a symlink or special file.");
83
+ if (stat.isDirectory())
84
+ walk(child, depth + 1);
85
+ else
86
+ files.push(relative(source.path, child).split(sep).join("/"));
87
+ if (files.length > 2048)
88
+ throw new Error("Skill contains more than 2048 resources.");
89
+ }
90
+ }
91
+ walk(source.path, 0);
92
+ if (!files.includes("SKILL.md"))
93
+ throw new Error("Skill entrypoint SKILL.md is missing.");
94
+ if (!files.includes(resource))
95
+ throw new Error("Resource is not in this Skill package.");
96
+ return { id, source, version: "current-source", resource, files, ...textFile(join(source.path, resource)) };
97
+ }
98
+ /** The reference must come from a stored Session/Run, never from client paths. */
99
+ export function readFrozenSkill(ref, resource) {
100
+ const manifest = verifySkillPackage(ref);
101
+ const root = join(dirname(ref.manifestPath), "files");
102
+ const entrypoint = relative(root, join(ref.path, "SKILL.md")).split(sep).join("/");
103
+ const selected = resource ?? entrypoint;
104
+ if (!manifest.files.some(file => file.path === selected))
105
+ throw new Error("Resource is not in this frozen Skill package.");
106
+ return { id: ref.id, source: ref.source, version: ref.digest, resource: selected,
107
+ files: manifest.files.map(file => file.path), ...textFile(join(root, selected)) };
108
+ }
@@ -0,0 +1,161 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ const MAX_FILES = 2048;
5
+ const MAX_BYTES = 32 * 1024 * 1024;
6
+ const MAX_FILE_BYTES = 8 * 1024 * 1024;
7
+ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
8
+ /** A package is data: no script, hook or executable is invoked while loading it.
9
+ * Builtin Skills share one tree so their sibling-relative references stay valid. */
10
+ export function snapshotSkillPackage(input) {
11
+ const root = resolve(input.root);
12
+ assertPhysical(root);
13
+ const files = [];
14
+ const contents = new Map();
15
+ let byteSize = 0;
16
+ function walk(directory, depth) {
17
+ if (depth > 32)
18
+ throw new Error("Skill package directory depth exceeds 32.");
19
+ for (const name of readdirSync(directory).sort()) {
20
+ if (name.includes("\\") || /[\x00-\x1f\x7f]/u.test(name)) {
21
+ throw new Error(`Invalid Skill resource name: ${JSON.stringify(name)}.`);
22
+ }
23
+ const path = join(directory, name);
24
+ const stat = lstatSync(path);
25
+ if (stat.isDirectory()) {
26
+ walk(path, depth + 1);
27
+ continue;
28
+ }
29
+ if (!stat.isFile())
30
+ throw new Error(`Skill package requires regular files, not symlinks or special files: ${path}.`);
31
+ if (files.length >= MAX_FILES || stat.size > MAX_FILE_BYTES || byteSize + stat.size > MAX_BYTES) {
32
+ throw new Error(`Skill package exceeds limits (${MAX_FILES} files, 8 MiB/file, 32 MiB total).`);
33
+ }
34
+ const bytes = readFileSync(path);
35
+ if (bytes.length !== stat.size)
36
+ throw new Error(`Skill resource changed while reading: ${path}.`);
37
+ byteSize += bytes.length;
38
+ const entry = { path: relative(root, path).split(sep).join("/"), digest: hash(bytes),
39
+ size: bytes.length, executable: (stat.mode & 0o111) !== 0 };
40
+ files.push(entry);
41
+ contents.set(entry.path, bytes);
42
+ }
43
+ }
44
+ walk(root, 0);
45
+ files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
46
+ const subdirectory = input.subdirectory ?? "";
47
+ const entrypoint = subdirectory ? `${subdirectory}/SKILL.md` : "SKILL.md";
48
+ if (!contents.has(entrypoint))
49
+ throw new Error(`Configured Skill not found: ${input.id}.`);
50
+ // Names, raw bytes and executable intent all participate; mtimes and source
51
+ // locations do not. Identical bundles deduplicate across Roles and Sessions.
52
+ const digest = hash(JSON.stringify(files));
53
+ const parent = resolve(input.home, "runtime", "skill-packages");
54
+ mkdirSync(parent, { recursive: true, mode: 0o700 });
55
+ assertPhysical(parent);
56
+ const destination = join(parent, digest);
57
+ const manifest = { schemaVersion: 1, files, digest };
58
+ if (!existsSync(destination)) {
59
+ const staging = mkdtempSync(join(parent, ".staging-"));
60
+ try {
61
+ for (const file of files) {
62
+ const path = join(staging, "files", file.path);
63
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
64
+ writeFileSync(path, contents.get(file.path), { flag: "wx", mode: file.executable ? 0o500 : 0o400 });
65
+ }
66
+ writeFileSync(join(staging, "manifest.json"), JSON.stringify(manifest), { flag: "wx", mode: 0o400 });
67
+ try {
68
+ renameSync(staging, destination);
69
+ }
70
+ catch (error) {
71
+ if (!existsSync(destination))
72
+ throw error;
73
+ // Another producer can have won the same content-addressed destination.
74
+ }
75
+ }
76
+ finally {
77
+ rmSync(staging, { recursive: true, force: true });
78
+ }
79
+ }
80
+ const ref = {
81
+ id: input.id, path: join(destination, "files", subdirectory), digest,
82
+ source: { kind: input.kind, path: join(root, subdirectory) },
83
+ manifestPath: join(destination, "manifest.json"), fileCount: files.length, byteSize
84
+ };
85
+ verifySkillPackage(ref);
86
+ return ref;
87
+ }
88
+ export function validateSkillPackageRef(ref) {
89
+ if (ref === null || typeof ref !== "object"
90
+ || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(ref.id)
91
+ || !/^[a-f0-9]{64}$/u.test(ref.digest)
92
+ || typeof ref.path !== "string" || !isAbsolute(ref.path)
93
+ || typeof ref.manifestPath !== "string" || !isAbsolute(ref.manifestPath)
94
+ || basename(dirname(ref.manifestPath)) !== ref.digest || basename(ref.manifestPath) !== "manifest.json"
95
+ || !ref.source || !["builtin", "configured"].includes(ref.source.kind)
96
+ || typeof ref.source.path !== "string" || !isAbsolute(ref.source.path)
97
+ || !Number.isSafeInteger(ref.fileCount) || ref.fileCount < 1 || ref.fileCount > MAX_FILES
98
+ || !Number.isSafeInteger(ref.byteSize) || ref.byteSize < 0 || ref.byteSize > MAX_BYTES) {
99
+ throw new Error("Skill package reference is invalid.");
100
+ }
101
+ }
102
+ /** Fail closed on lost/corrupt snapshots. Never refill them from mutable sources. */
103
+ export function verifySkillPackage(ref) {
104
+ validateSkillPackageRef(ref);
105
+ const root = join(dirname(ref.manifestPath), "files");
106
+ assertPhysical(dirname(ref.manifestPath));
107
+ assertPhysical(ref.manifestPath);
108
+ if (lstatSync(ref.manifestPath).size > 1024 * 1024)
109
+ throw new Error("Skill package manifest exceeds limits.");
110
+ const manifest = JSON.parse(readFileSync(ref.manifestPath, "utf8"));
111
+ if (manifest.schemaVersion !== 1 || !Array.isArray(manifest.files)
112
+ || manifest.digest !== ref.digest || hash(JSON.stringify(manifest.files)) !== ref.digest
113
+ || manifest.files.length !== ref.fileCount)
114
+ throw new Error("Skill package manifest digest mismatch.");
115
+ const entrypoint = relative(root, join(ref.path, "SKILL.md")).split(sep).join("/");
116
+ let byteSize = 0;
117
+ const paths = new Set();
118
+ for (const file of manifest.files) {
119
+ if (typeof file.path !== "string" || !file.path || file.path.split("/").some((part) => part === ".." || part === "." || part === "")
120
+ || file.path.includes("\\") || isAbsolute(file.path) || paths.has(file.path)) {
121
+ throw new Error("Skill package resource path is invalid.");
122
+ }
123
+ paths.add(file.path);
124
+ const path = join(root, file.path);
125
+ assertPhysical(path);
126
+ const stat = lstatSync(path);
127
+ if (!stat.isFile() || stat.size !== file.size || stat.size > MAX_FILE_BYTES
128
+ || ((stat.mode & 0o111) !== 0) !== file.executable
129
+ || hash(readFileSync(path)) !== file.digest)
130
+ throw new Error(`Skill package resource changed: ${file.path}.`);
131
+ byteSize += file.size;
132
+ }
133
+ if (!paths.has(entrypoint) || byteSize !== ref.byteSize)
134
+ throw new Error("Skill package entrypoint or size mismatch.");
135
+ let observed = 0;
136
+ function checkInventory(directory, depth) {
137
+ if (depth > 32)
138
+ throw new Error("Skill package directory depth exceeds 32.");
139
+ for (const name of readdirSync(directory)) {
140
+ const path = join(directory, name);
141
+ const stat = lstatSync(path);
142
+ if (stat.isDirectory()) {
143
+ checkInventory(path, depth + 1);
144
+ continue;
145
+ }
146
+ if (!stat.isFile() || !paths.has(relative(root, path).split(sep).join("/"))) {
147
+ throw new Error("Skill package contains an unrecorded resource.");
148
+ }
149
+ observed++;
150
+ }
151
+ }
152
+ checkInventory(root, 0);
153
+ if (observed !== paths.size)
154
+ throw new Error("Skill package inventory changed.");
155
+ return manifest;
156
+ }
157
+ function assertPhysical(path) {
158
+ if (realpathSync(path) !== resolve(path)) {
159
+ throw new Error(`Skill package paths must not contain symlinks: ${path}.`);
160
+ }
161
+ }
@@ -580,7 +580,8 @@ function freeze(value) {
580
580
  }
581
581
  function summarize(value) {
582
582
  const record = value;
583
- return ["title", "objective", "question", "summary", "body", "status", "type"]
584
- .flatMap((key) => typeof record[key] === "string" ? [`${key}: ${record[key]}`] : [])
583
+ return ["title", "scope", "expiresWhen", "version", "objective", "question", "summary", "body", "status", "type"]
584
+ .flatMap((key) => typeof record[key] === "string" || (key === "version" && typeof record[key] === "number")
585
+ ? [`${key}: ${record[key]}`] : [])
585
586
  .join("; ").slice(0, 400);
586
587
  }
@@ -0,0 +1,127 @@
1
+ import { usageError } from "../errors/cliError.js";
2
+ import { TASK_SEARCH_KINDS } from "../storage/taskSearch.js";
3
+ import { contextContentDigest } from "./contextSnapshot.js";
4
+ import { taskCatalogScope } from "./taskCatalog.js";
5
+ import { readTaskContextResource } from "./taskContext.js";
6
+ const PAGE_BYTES = 32 * 1024;
7
+ /** Human-readable field pages, using the same source authorization and version
8
+ * checks as Context. Offsets are Unicode code points, like search snippets. */
9
+ export function readTaskSearchSource(store, taskId, input, environment = {}) {
10
+ if (!input.digest || !Number.isSafeInteger(input.offset) || input.offset < 0) {
11
+ throw usageError("A pinned digest and non-negative source offset are required.");
12
+ }
13
+ const fields = {
14
+ "task-brief": /^\$\.(objective|technicalApproach|currentFocus|leaderSummary|boundaries\[\d+\])$/,
15
+ "task-message": /^\$\.body$/,
16
+ "task-decision": /^\$\.(title|rationale|supersededReason)$/,
17
+ "run": /^\$\.result\.output$/,
18
+ "task": /^\$\.completionSummary$/
19
+ };
20
+ if (!fields[input.store]?.test(input.field))
21
+ throw usageError("Unsupported search source field.");
22
+ const original = readTaskContextResource(store, taskId, input, environment);
23
+ let value = original.value;
24
+ for (const part of input.field.slice(2).replace(/\[(\d+)\]/g, ".$1").split(".")) {
25
+ value = value && typeof value === "object" ? value[part] : undefined;
26
+ }
27
+ if (typeof value !== "string")
28
+ throw usageError("Search source text is unavailable.");
29
+ const points = Array.from(value);
30
+ if (input.offset > points.length)
31
+ throw usageError("Source offset is outside the text.");
32
+ const end = Math.min(input.offset + 4000, points.length);
33
+ return { taskId, ref: original.ref, field: input.field, authority: "reference-only",
34
+ content: points.slice(input.offset, end).join(""), offset: input.offset,
35
+ nextOffset: end < points.length ? end : null, totalCharacters: points.length,
36
+ offsetUnit: "unicode-code-points" };
37
+ }
38
+ export function parseTaskSearchOptions(args) {
39
+ const query = args[0]?.trim();
40
+ if (!query || query.length > 256 || query.includes("\0"))
41
+ throw usageError("Task search requires a query of 1..256 characters.");
42
+ const values = new Map();
43
+ for (let i = 1; i < args.length; i += 2) {
44
+ const key = args[i];
45
+ const value = args[i + 1];
46
+ if (!["--task", "--kind", "--limit", "--cursor"].includes(key) || values.has(key)
47
+ || value === undefined || value.startsWith("--"))
48
+ throw usageError("Task search expects query [--task <id>] [--kind <kind>] [--limit <1..100>] [--cursor <cursor>].");
49
+ values.set(key, value);
50
+ }
51
+ const limit = values.has("--limit") ? Number(values.get("--limit")) : 20;
52
+ const kind = values.get("--kind");
53
+ const taskId = values.get("--task");
54
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
55
+ throw usageError("Task search limit must be between 1 and 100.");
56
+ if (kind !== undefined && !TASK_SEARCH_KINDS.includes(kind))
57
+ throw usageError("Unknown Task search kind.");
58
+ if (taskId !== undefined && !/^task-[1-9]\d*$/.test(taskId))
59
+ throw usageError("Invalid Task search target.");
60
+ return { query, limit, ...(kind ? { kind } : {}), ...(taskId ? { taskId } : {}),
61
+ ...(values.has("--cursor") ? { cursor: values.get("--cursor") } : {}) };
62
+ }
63
+ export function searchTaskBodies(store, options, environment = {}) {
64
+ // Validate typed callers as well as CLI/HTTP callers through one parser.
65
+ const valid = parseTaskSearchOptions([options.query, "--limit", String(options.limit),
66
+ ...(options.taskId === undefined ? [] : ["--task", options.taskId]),
67
+ ...(options.kind === undefined ? [] : ["--kind", options.kind]),
68
+ ...(options.cursor === undefined ? [] : ["--cursor", options.cursor])]);
69
+ return store.readTransaction(reader => {
70
+ const callerTask = taskCatalogScope(reader, environment);
71
+ if (callerTask !== undefined && valid.taskId !== undefined && callerTask !== valid.taskId) {
72
+ throw usageError("Search target is outside the caller's Task.");
73
+ }
74
+ const taskId = callerTask ?? valid.taskId;
75
+ const scope = contextContentDigest({ taskId: taskId ?? null, query: valid.query, kind: valid.kind ?? null });
76
+ const after = valid.cursor === undefined ? undefined : decodeCursor(valid.cursor, scope);
77
+ const rows = reader.queryTaskSearch({ ...valid, taskId, after, limit: valid.limit + 1 });
78
+ const items = [];
79
+ let nextCursor = null;
80
+ const response = () => ({
81
+ query: valid.query, scope: { taskId: taskId ?? null, archived: "included" },
82
+ items, nextCursor, consistency: "current-per-page; repeat search after source changes",
83
+ matching: "literal substring; ASCII case-insensitive; Unicode otherwise exact",
84
+ limits: { maxItems: valid.limit, pageBytes: PAGE_BYTES, snippetCodePoints: 320 },
85
+ reuse: "References are evidence, not execution authority. An authorized reader may supply selected source text and its reference to a new Task; that Task plans and obtains its own authority."
86
+ });
87
+ for (const row of rows.slice(0, valid.limit)) {
88
+ // Reuse the exact Context permission and digest contract for each hit.
89
+ const original = readTaskContextResource(reader, row.taskId, { store: row.store, refId: row.refId }, environment);
90
+ const item = { taskId: row.taskId, kind: row.kind, field: row.field, snippet: row.snippet,
91
+ offset: row.offset, ref: original.ref, authority: "reference-only" };
92
+ const previous = nextCursor;
93
+ nextCursor = encodeCursor(scope, row);
94
+ items.push(item);
95
+ if (Buffer.byteLength(JSON.stringify({ ok: true, data: response() })) > PAGE_BYTES) {
96
+ items.pop();
97
+ nextCursor = previous;
98
+ break;
99
+ }
100
+ }
101
+ if (rows.length === items.length)
102
+ nextCursor = null;
103
+ if (rows.length && !items.length)
104
+ throw usageError("Task search identity exceeds its page budget.");
105
+ return response();
106
+ });
107
+ }
108
+ function encodeCursor(scope, row) {
109
+ return Buffer.from(JSON.stringify({ version: 1, scope, after: {
110
+ taskId: row.taskId, kind: row.kind, refId: row.refId, field: row.field
111
+ } })).toString("base64url");
112
+ }
113
+ function decodeCursor(raw, scope) {
114
+ try {
115
+ if (raw.length > 4096 || !/^[A-Za-z0-9_-]+$/.test(raw))
116
+ throw new Error();
117
+ const value = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
118
+ if (value.version !== 1 || value.scope !== scope || !value.after
119
+ || !["taskId", "kind", "refId", "field"].every(key => typeof value.after[key] === "string"
120
+ && value.after[key].length > 0 && value.after[key].length <= 256))
121
+ throw new Error();
122
+ return value.after;
123
+ }
124
+ catch {
125
+ throw usageError("Invalid Task search cursor or cursor belongs to another scope/query.");
126
+ }
127
+ }
@@ -36,7 +36,7 @@ import { runConfigOverview } from "./commands/configOverview.js";
36
36
  import { parseControllerCleanupOptions, parseControllerRuntimeSnapshot, parseControllerStatusOptions, renderControllerResourceStatus, renderRuntimeIdentitySection, runInteractiveControllerCleanup, summarizeDurablePhysicalMismatch } from "./commands/controllerCommands.js";
37
37
  import { runDurableJobCommand } from "./commands/durableJobCommands.js";
38
38
  import { parseExecutionAuditOptions, runExecutionAuditCommand } from "./commands/executionAuditCommands.js";
39
- import { runGlobalRoleCommand } from "./commands/globalRoleCommands.js";
39
+ import { runGlobalRoleCommand, previewGlobalRoleAgentConfigurationMutation } from "./commands/globalRoleCommands.js";
40
40
  import { runJobCommand } from "./commands/jobCommands.js";
41
41
  import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
42
42
  import { previewProfileAgentConfigurationMutation, runProfileCommand } from "./commands/profileCommands.js";
@@ -110,7 +110,7 @@ import { inspectStorageSchema } from "./storage/storageSchema.js";
110
110
  import { resolveYuiHome } from "./storage/taskStore.js";
111
111
  import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
112
112
  import { inspectTaskArchive, renderTaskArchivePreflight } from "./task/archivePreflight.js";
113
- import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./task/remoteDeliveryService.js";
113
+ import { archiveOrdinaryTask } from "./task/ordinaryArchive.js";
114
114
  import { resolveTaskRecordReference } from "./task/taskRecordReference.js";
115
115
  import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
116
116
  import { NodeCommandExecutor } from "./tmux/commandExecutor.js";
@@ -704,7 +704,8 @@ export async function main() {
704
704
  const domain = resolved[1];
705
705
  const roleOptions = {
706
706
  yuiHome: home,
707
- env: process.env
707
+ env: process.env,
708
+ ...(validateAgentConfiguration === undefined ? {} : { validateAgentConfiguration })
708
709
  };
709
710
  if (domain === "show") {
710
711
  const result = runConfigOverview(resolved.slice(2), store, process.env, roleOptions);
@@ -1039,7 +1040,7 @@ export async function main() {
1039
1040
  const action = resolved[2];
1040
1041
  const taskId = resolved[3];
1041
1042
  const usage = "Usage: yui task artifact list <task> | read <task> <relative-path> [<commit>] | "
1042
- + "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
1043
+ + "save <task> <relative-path> <content> [--encoding utf8|base64] [--message <text>] [--expected-head <commit>]";
1043
1044
  if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
1044
1045
  throw usageError(usage);
1045
1046
  }
@@ -1073,6 +1074,7 @@ export async function main() {
1073
1074
  const rest = resolved.slice(6);
1074
1075
  let message;
1075
1076
  let expectedHead;
1077
+ let encoding;
1076
1078
  for (let index = 0; index < rest.length; index += 1) {
1077
1079
  const value = rest[index + 1];
1078
1080
  if (rest[index] === "--message" && value !== undefined) {
@@ -1085,10 +1087,15 @@ export async function main() {
1085
1087
  index += 1;
1086
1088
  continue;
1087
1089
  }
1090
+ if (rest[index] === "--encoding" && (value === "utf8" || value === "base64")) {
1091
+ encoding = value;
1092
+ index += 1;
1093
+ continue;
1094
+ }
1088
1095
  throw usageError(usage);
1089
1096
  }
1090
1097
  data = await saveArtifactCapability(home, taskId, {
1091
- relativePath, content,
1098
+ relativePath, content, ...(encoding === undefined ? {} : { encoding }),
1092
1099
  ...(message === undefined ? {} : { message }),
1093
1100
  ...(expectedHead === undefined ? {} : { expectedHead })
1094
1101
  });
@@ -1320,8 +1327,6 @@ export async function main() {
1320
1327
  });
1321
1328
  return;
1322
1329
  }
1323
- let archiveRemoteDeliveryProof;
1324
- let archiveTaskReviewCandidate;
1325
1330
  if (resolved[1] === "archive") {
1326
1331
  const request = parseTaskArchiveArguments(resolved.slice(2));
1327
1332
  if (request.sourceMessage !== undefined) {
@@ -1359,15 +1364,17 @@ export async function main() {
1359
1364
  emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
1360
1365
  return;
1361
1366
  }
1367
+ if (disposition === "integrated") {
1368
+ const result = await archiveOrdinaryTask(workspaceCoordinator, taskId, {
1369
+ runtime, environment: process.env, yuiHome: home
1370
+ });
1371
+ emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(result)}`, false, result);
1372
+ return;
1373
+ }
1362
1374
  {
1363
- if (disposition === "integrated") {
1364
- archiveTaskReviewCandidate = task.status === "cancelled" ? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env) : undefined;
1365
- archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
1366
- assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
1367
- }
1368
1375
  await workspaceCoordinator.prepareTaskForArchive(task.id);
1369
1376
  runTaskCommand(resolved.slice(1), store, {
1370
- runtime, environment: process.env, yuiHome: home, archiveRemoteDeliveryProof
1377
+ runtime, environment: process.env, yuiHome: home
1371
1378
  });
1372
1379
  await workspaceCoordinator.cleanupArchivedTask(task.id, disposition);
1373
1380
  const current = store.getTask(task.id);
@@ -1527,9 +1534,7 @@ export async function main() {
1527
1534
  laneDispatchProjectPaths = preparedLanes.projectPaths;
1528
1535
  }
1529
1536
  const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1530
- const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
1531
- ? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
1532
- : archiveTaskReviewCandidate;
1537
+ const actualTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
1533
1538
  const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
1534
1539
  // Read-only Host evidence for Session inspect and Role status/list. The command stays
1535
1540
  // synchronous over persisted state; this is the live reading it prints
@@ -1569,9 +1574,6 @@ export async function main() {
1569
1574
  ...(actualTaskReviewCandidate === undefined
1570
1575
  ? {}
1571
1576
  : { actualTaskReviewCandidate }),
1572
- ...(archiveRemoteDeliveryProof === undefined
1573
- ? {}
1574
- : { archiveRemoteDeliveryProof }),
1575
1577
  ...(deltaRecheckPreflight === undefined
1576
1578
  ? {}
1577
1579
  : { deltaRecheckPreflight }),
@@ -2721,7 +2723,10 @@ function selectionPorts(store, catalogs) {
2721
2723
  }
2722
2724
  async function preflightAgentConfigurationMutation(commandArgs, store, catalogs) {
2723
2725
  const mutation = profileAgentConfigurationMutation(commandArgs, store)
2724
- ?? taskRoleAgentConfigurationMutation(commandArgs, store);
2726
+ ?? taskRoleAgentConfigurationMutation(commandArgs, store)
2727
+ ?? (commandArgs[0] === "config" && commandArgs[1] === "role"
2728
+ ? previewGlobalRoleAgentConfigurationMutation(commandArgs.slice(2), store, { yuiHome: store.rootDirectory() })
2729
+ : undefined);
2725
2730
  if (mutation === undefined) {
2726
2731
  await warmConfigurationMutationCatalog(commandArgs, store, catalogs);
2727
2732
  return undefined;
@@ -35,7 +35,7 @@ import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } fro
35
35
  import { providerContinuationKey } from "../runtime/providerContinuation.js";
36
36
  import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
37
37
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
38
- import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
38
+ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeUsageEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
39
39
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
40
40
  import { enqueueSupervisorEvent, recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
41
41
  import { RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT, foldRunProgressFacts, isRoleRunStalled, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey } from "../scheduler/roleRunStall.js";
@@ -1192,6 +1192,16 @@ export class FileSchedulerStoreAdapter {
1192
1192
  throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
1193
1193
  }
1194
1194
  const currentRun = binding.run;
1195
+ if (input.roleName === "leader" && input.attemptId.startsWith(`notification:${input.taskId}/`)) {
1196
+ const wakeId = input.attemptId.split("/")[1];
1197
+ const wake = store.listTaskWakes(input.taskId).find(w => w.id === wakeId);
1198
+ const ids = new Set(wake?.refs?.filter(r => r.type === "message").map(r => r.id));
1199
+ if (store.listMessages(input.taskId).some(m => ids.has(m.id)
1200
+ && m.inputControl?.expectedSessionId !== undefined
1201
+ && m.inputControl.expectedSessionId !== input.nativeSessionId)) {
1202
+ throw new AgentHostProviderTurnFenceError("Notification input belongs to the selected old Session; do not retarget.");
1203
+ }
1204
+ }
1195
1205
  if (session.effective.executionEnvironment !== undefined) {
1196
1206
  assertExecutionEnvironmentCurrent(store, input.taskId, session.effective.executionEnvironment);
1197
1207
  }
@@ -1248,7 +1258,7 @@ export class FileSchedulerStoreAdapter {
1248
1258
  // before the Provider write; provider-visible userMessage items alone
1249
1259
  // are NOT proof of human authorship (managed prompts use those too).
1250
1260
  // This shares the exact writer/session admission and its transaction.
1251
- if (input.authorityOwner === "human" && input.roleName === "leader"
1261
+ if ((input.authorityOwner === "human" || input.attemptId.startsWith(`human:${input.holderId}:`)) && input.roleName === "leader"
1252
1262
  && input.runId === undefined && input.boundedText !== undefined) {
1253
1263
  const prior = store.listEvents(input.taskId).find(event => event.type === "message.native-user-input" && event.payload.attemptId === input.attemptId);
1254
1264
  if (prior !== undefined) {
@@ -1770,7 +1780,7 @@ export class FileSchedulerStoreAdapter {
1770
1780
  directive: `Plan Task ${taskId} with the user. Persist requirements and decisions. Request activation explicitly; planning grants no delivery authority.`,
1771
1781
  contextSnapshotRef: contextSnapshotRef(snapshot),
1772
1782
  deltaRefIds: [] }), now, {
1773
- purpose: "planning", effective: resolveEffectiveLaunch({ role, purpose: "planning" })
1783
+ purpose: "planning", effective: resolveEffectiveLaunch({ store, role, purpose: "planning" })
1774
1784
  });
1775
1785
  store.saveRun(run);
1776
1786
  store.saveActiveRun(run);
@@ -1849,6 +1859,18 @@ export class FileSchedulerStoreAdapter {
1849
1859
  const sessions = store.getTaskRoleSessionSet(taskId, "leader");
1850
1860
  const session = sessions?.sessions[sessions.activeAgentId];
1851
1861
  const obsoleteIds = new Set();
1862
+ for (const message of store.listMessages(taskId)) {
1863
+ if (!pendingMessageIds.has(message.id) || message.inputControl?.expectedSessionId === undefined)
1864
+ continue;
1865
+ if (message.inputControl.expectedSessionId !== session?.nativeSessionId
1866
+ || message.continuation?.notDeliveredReason !== undefined) {
1867
+ obsoleteIds.add(message.id);
1868
+ if (message.continuation?.notDeliveredReason === undefined)
1869
+ store.updateMessage(taskId, {
1870
+ ...message, continuation: { notDeliveredReason: "selected-native-session-changed" }
1871
+ });
1872
+ }
1873
+ }
1852
1874
  for (const message of claimedMessages) {
1853
1875
  const claim = message.interruptThen;
1854
1876
  if (claim.notDeliveredReason !== undefined
@@ -3136,6 +3158,7 @@ function mapRole(store, role) {
3136
3158
  // Active AgentRun delivery separately and exclusively uses turn.effective.
3137
3159
  const effective = reopened || (liveSession !== null && liveSession.agentId !== role.activeAgentId)
3138
3160
  ? resolveEffectiveLaunch({
3161
+ store,
3139
3162
  role,
3140
3163
  purpose,
3141
3164
  ...(workspace === undefined ? {} : { workspace }),
@@ -3144,6 +3167,7 @@ function mapRole(store, role) {
3144
3167
  : liveSession !== null && workspace?.owner.type === "task"
3145
3168
  ? effectiveLaunchWithTaskMainWorkspace(liveSession.effective, workspace)
3146
3169
  : liveSession?.effective ?? resolveEffectiveLaunch({
3170
+ store,
3147
3171
  role,
3148
3172
  purpose,
3149
3173
  ...(workspace === undefined ? {} : { workspace }),
@@ -3199,6 +3223,8 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
3199
3223
  return effective;
3200
3224
  }
3201
3225
  function globalSessionEffective(role, existing) {
3226
+ // A native hook without a captured launch is not proof of loaded resources.
3227
+ // Actual controlled launches supply their frozen effective snapshot explicitly.
3202
3228
  return existing?.effective ?? resolveEffectiveLaunch({ role, purpose: "execution" });
3203
3229
  }
3204
3230
  function mapSession(session) {
@@ -3292,10 +3318,10 @@ function compactedRuntimeObservationIds(events, incoming) {
3292
3318
  });
3293
3319
  const remove = ({ observation }) => {
3294
3320
  if (incoming.kind === "activity.observed") {
3295
- if (isRuntimeTokenEvidence(incoming))
3321
+ if (isRuntimeUsageEvidence(incoming))
3296
3322
  return false;
3297
3323
  return observation.kind === "activity.observed"
3298
- && !isRuntimeTokenEvidence(observation);
3324
+ && !isRuntimeUsageEvidence(observation);
3299
3325
  }
3300
3326
  if (incoming.kind === "operation.started") {
3301
3327
  return (observation.kind === "operation.started"
@@ -3549,7 +3575,7 @@ function usageSnapshotIsSuperseded(events, incoming) {
3549
3575
  && observationIsStrictlyNewer(observation, incoming));
3550
3576
  }
3551
3577
  function hasPersistedRuntimeObservation(events, incoming) {
3552
- const usageIdentity = isRuntimeTokenEvidence(incoming)
3578
+ const usageIdentity = isRuntimeUsageEvidence(incoming)
3553
3579
  ? incoming.eventId
3554
3580
  : undefined;
3555
3581
  return events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT