@stigmer/runner 3.12.4 → 3.12.6

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 (180) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/call-agent.js +19 -6
  3. package/dist/activities/call-agent.js.map +1 -1
  4. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  5. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  6. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  7. package/dist/activities/execute-cursor/error-classifier.d.ts +9 -0
  8. package/dist/activities/execute-cursor/error-classifier.js +30 -1
  9. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  10. package/dist/activities/execute-cursor/index.d.ts +10 -1
  11. package/dist/activities/execute-cursor/index.js +62 -36
  12. package/dist/activities/execute-cursor/index.js.map +1 -1
  13. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  14. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  15. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  16. package/dist/activities/execute-cursor/service-tier.d.ts +38 -29
  17. package/dist/activities/execute-cursor/service-tier.js +92 -63
  18. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  19. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  20. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  21. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  22. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  23. package/dist/activities/execute-cursor/usage-accumulator.d.ts +16 -2
  24. package/dist/activities/execute-cursor/usage-accumulator.js +12 -2
  25. package/dist/activities/execute-cursor/usage-accumulator.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
  27. package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
  28. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  29. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
  30. package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
  31. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
  32. package/dist/activities/execute-deep-agent/index.js +57 -5
  33. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  34. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
  35. package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
  36. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  37. package/dist/activities/execute-deep-agent/setup.js +17 -16
  38. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  39. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  40. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  41. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/middleware/path-normalization.d.ts +49 -49
  45. package/dist/middleware/path-normalization.js +73 -84
  46. package/dist/middleware/path-normalization.js.map +1 -1
  47. package/dist/middleware/types.d.ts +3 -3
  48. package/dist/runner-manager.d.ts +1 -1
  49. package/dist/runner-manager.js +22 -15
  50. package/dist/runner-manager.js.map +1 -1
  51. package/dist/runner.js +11 -0
  52. package/dist/runner.js.map +1 -1
  53. package/dist/shared/args-preview.d.ts +8 -0
  54. package/dist/shared/args-preview.js +14 -3
  55. package/dist/shared/args-preview.js.map +1 -1
  56. package/dist/shared/artifact-storage.d.ts +11 -1
  57. package/dist/shared/artifact-storage.js +10 -1
  58. package/dist/shared/artifact-storage.js.map +1 -1
  59. package/dist/shared/caller-identity.d.ts +10 -7
  60. package/dist/shared/caller-identity.js +10 -7
  61. package/dist/shared/caller-identity.js.map +1 -1
  62. package/dist/shared/declared-preferences.d.ts +47 -0
  63. package/dist/shared/declared-preferences.js +64 -0
  64. package/dist/shared/declared-preferences.js.map +1 -0
  65. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  66. package/dist/shared/mcp-enabled-tools.js +2 -3
  67. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  68. package/dist/shared/mcp-resolver.d.ts +20 -15
  69. package/dist/shared/mcp-resolver.js +11 -12
  70. package/dist/shared/mcp-resolver.js.map +1 -1
  71. package/dist/shared/placeholder-resolver.d.ts +9 -2
  72. package/dist/shared/placeholder-resolver.js +9 -2
  73. package/dist/shared/placeholder-resolver.js.map +1 -1
  74. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  75. package/dist/shared/plan-mode-permissions.js +27 -55
  76. package/dist/shared/plan-mode-permissions.js.map +1 -1
  77. package/dist/shared/skill-mount.d.ts +89 -0
  78. package/dist/shared/skill-mount.js +142 -0
  79. package/dist/shared/skill-mount.js.map +1 -0
  80. package/dist/shared/skill-writer.d.ts +28 -26
  81. package/dist/shared/skill-writer.js +79 -102
  82. package/dist/shared/skill-writer.js.map +1 -1
  83. package/dist/shared/thinking-mode.d.ts +35 -0
  84. package/dist/shared/thinking-mode.js +43 -0
  85. package/dist/shared/thinking-mode.js.map +1 -0
  86. package/dist/shared/worker-shutdown.d.ts +67 -0
  87. package/dist/shared/worker-shutdown.js +79 -0
  88. package/dist/shared/worker-shutdown.js.map +1 -0
  89. package/dist/shared/workspace/types.d.ts +3 -2
  90. package/dist/workflow-engine/loader.js +82 -6
  91. package/dist/workflow-engine/loader.js.map +1 -1
  92. package/dist/workflow-engine/tasks/human-input.d.ts +2 -1
  93. package/dist/workflow-engine/tasks/human-input.js +31 -3
  94. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  95. package/dist/workflow-engine/types.d.ts +17 -3
  96. package/dist/workflow-engine/types.js.map +1 -1
  97. package/dist/workflows/human-input-orchestrator.d.ts +4 -0
  98. package/dist/workflows/human-input-orchestrator.js +13 -0
  99. package/dist/workflows/human-input-orchestrator.js.map +1 -1
  100. package/package.json +15 -11
  101. package/src/activities/call-agent.ts +20 -6
  102. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  103. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  104. package/src/activities/execute-cursor/__tests__/error-classifier-billing.test.ts +67 -0
  105. package/src/activities/execute-cursor/__tests__/service-tier.test.ts +67 -7
  106. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  107. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +33 -1
  108. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  109. package/src/activities/execute-cursor/error-classifier.ts +42 -1
  110. package/src/activities/execute-cursor/index.ts +78 -41
  111. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  112. package/src/activities/execute-cursor/service-tier.ts +94 -63
  113. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  114. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  115. package/src/activities/execute-cursor/usage-accumulator.ts +11 -1
  116. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  117. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  118. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  119. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  120. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  121. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  122. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  123. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  124. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  125. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  126. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  127. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  128. package/src/activities/execute-deep-agent/index.ts +57 -5
  129. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  130. package/src/activities/execute-deep-agent/setup.ts +21 -19
  131. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  132. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  133. package/src/index.ts +1 -1
  134. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  135. package/src/middleware/path-normalization.ts +78 -90
  136. package/src/middleware/types.ts +3 -3
  137. package/src/runner-manager.ts +26 -16
  138. package/src/runner.ts +14 -0
  139. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  140. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  141. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  142. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  143. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  144. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  145. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  146. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  147. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  148. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  149. package/src/shared/args-preview.ts +17 -3
  150. package/src/shared/artifact-storage.ts +20 -1
  151. package/src/shared/caller-identity.ts +10 -7
  152. package/src/shared/declared-preferences.ts +84 -0
  153. package/src/shared/mcp-enabled-tools.ts +2 -3
  154. package/src/shared/mcp-resolver.ts +20 -20
  155. package/src/shared/placeholder-resolver.ts +9 -2
  156. package/src/shared/plan-mode-permissions.ts +27 -58
  157. package/src/shared/skill-mount.ts +179 -0
  158. package/src/shared/skill-writer.ts +96 -130
  159. package/src/shared/thinking-mode.ts +52 -0
  160. package/src/shared/worker-shutdown.ts +99 -0
  161. package/src/shared/workspace/types.ts +3 -2
  162. package/src/workflow-engine/__tests__/loader.test.ts +95 -0
  163. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +156 -0
  164. package/src/workflow-engine/loader.ts +97 -6
  165. package/src/workflow-engine/tasks/human-input.ts +41 -6
  166. package/src/workflow-engine/types.ts +18 -3
  167. package/src/workflows/human-input-orchestrator.ts +20 -2
  168. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  169. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  170. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  171. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  172. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  173. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  174. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  175. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  176. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  177. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  178. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  179. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  180. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -2,26 +2,25 @@
2
2
  * The Plan-mode filesystem permission rules — the enforcement twin of
3
3
  * `plan-mode-prompt.ts` (which carries the instruction half of the contract).
4
4
  *
5
- * Plan mode is contained BY CONSTRUCTION on the native harness: these rules
6
- * deny every filesystem write everywhere AND scope reads to the session
7
- * workspace (issue #528 owner ruling: the workspace is plan mode's read
8
- * boundary on cloud and desktop runners alike). Without the read boundary,
9
- * model-provided absolute paths reached anywhere the process account could
10
- * read the pod filesystem (including /proc/self/environ) on cloud runners,
11
- * the user's whole home directory on desktop while plan mode still carries
12
- * exfiltration-capable tools (web_fetch, MCP). Rules are first-match-wins
13
- * with a permissive default (deepagents' decidePathAccess), so order is
14
- * load-bearing: the workspace read-allow must precede the read-deny.
5
+ * Plan mode's policy is: reads anywhere in the workspace, writes nowhere.
6
+ * Only the write half needs a RULE. The read boundary that issue #528 built
7
+ * out of rules (workspace read-allow + read-deny, glob-escaped real root) is
8
+ * now STRUCTURAL: every native-harness backend is virtual-rooted
9
+ * (`virtualMode: true` see cas-capture-backend.ts, issue #754), so every
10
+ * path a tool can express resolves inside the workspace root and traversal
11
+ * is rejected at resolution. A rule cannot widen that, and no workspace-root
12
+ * glob is needed to narrow it — which also retires the whole
13
+ * special-characters-in-the-root hazard (`escapeGlobLiteral` and its
14
+ * end-to-end matcher suite) that the real-path rules carried.
15
15
  *
16
- * The workspace-root pattern is matched as a STRING against the raw tool-call
17
- * path (micromatch, dot:true), before the backend touches disk. That is
18
- * exactly why the legitimate out-of-workspace reads keep working: skills,
19
- * attachments, and the approved plan live in the platform dir but are
20
- * addressed through the `{workspace}/.stigmer` symlink (see
21
- * shared/workspace/stigmer-link.ts), so their path strings are in-root even
22
- * though the bytes are not. The same holds for multi-workspace local entries
23
- * (`{workspace}/{name}` symlinks). A realpath-based boundary would break
24
- * both; do not "harden" this into one.
16
+ * Paths in these rules are VIRTUAL: "/" is the workspace root, matched by
17
+ * micromatch against the raw tool-call path (dot:true) after the
18
+ * path-normalization middleware has canonicalized it (relative "/"-rooted;
19
+ * see middleware/path-normalization.ts). The legitimate platform-dir reads
20
+ * (skills, attachments, the approved plan) keep working exactly as before:
21
+ * they are addressed through the `{workspace}/.stigmer` symlink
22
+ * (shared/workspace/stigmer-link.ts), an in-root path string in either
23
+ * dialect.
25
24
  *
26
25
  * Applied in execute-deep-agent/setup.ts to the parent graph AND threaded
27
26
  * into every compiled sub-agent graph: deepagents' parent-permission
@@ -30,50 +29,23 @@
30
29
  * (issue #255). Kept as its own side-effect-free module so tests can pin the
31
30
  * production rules without dragging in setup.ts's import graph.
32
31
  *
33
- * Rules travel with a companion: every graph that carries them also installs
34
- * the path-normalization middleware (middleware/path-normalization.ts,
35
- * issues #429/#528), because deepagents' rule validation refuses
36
- * workspace-relative paths outright and its `ls`/`glob`/`grep` schema default
37
- * of "/" would deny the bare first listing. Both are derived from the same
38
- * expression at each composition site so they cannot drift apart.
39
- *
40
32
  * Invariant: never combine these rules with a shell-capable (sandbox)
41
33
  * backend — deepagents rejects that pairing at graph construction (see the
42
34
  * cas-capture-backend.ts header). Plan mode guarantees it by construction:
43
35
  * it is the mode that clears `shellEnv`, and `shellEnv` is the single switch
44
36
  * for shell capability on both the parent and sub-agent backends.
45
37
  */
46
- import { resolve } from "node:path";
47
- /**
48
- * Backslash-escape every character micromatch/picomatch treats as glob
49
- * syntax, so the result matches the input literally. micromatch exports no
50
- * escape API of its own, and this is correctness, not caution: an unescaped
51
- * `(` in a desktop project path would make the workspace read-allow rule
52
- * silently never match — bricking every plan-mode read for that workspace.
53
- * Semantics are pinned end-to-end through deepagents' own matcher by the
54
- * special-character workspace suite in plan-mode-path-normalization.test.ts.
55
- */
56
- export function escapeGlobLiteral(literal) {
57
- return literal.replace(/[\\*?()[\]{}!+@]/g, "\\$&");
58
- }
59
38
  /**
60
- * Build the plan-mode rule set for a graph whose filesystem backend is
61
- * rooted at `workspaceRootDir`. The three rules read as the policy:
62
- * reads allowed in the workspace, reads denied everywhere else, writes
63
- * denied everywhere.
39
+ * Build the plan-mode rule set. One rule IS the policy: writes denied
40
+ * everywhere ("/**" is every virtual path; deepagents evaluates
41
+ * first-match-wins with a permissive default, so reads stay allowed
42
+ * workspace-confined structurally, not by rule).
64
43
  *
65
- * `{root}/**` matches the root itself as well as its subtree (verified
66
- * against the installed micromatch), so one allow pattern suffices. The
67
- * root is `path.resolve`d first because enforcement canonicalizes incoming
68
- * paths (collapsed slashes, no trailing separator) before matching — a
69
- * trailing slash in the pattern would silently match nothing.
44
+ * Deliberately parameterless: the pre-#754 signature took the workspace root
45
+ * to build a real-path read fence; resurrecting a root-derived rule would
46
+ * silently mismatch the virtual dialect the enforcement now sees.
70
47
  */
71
- export function buildPlanModePermissions(workspaceRootDir) {
72
- const canonicalRoot = resolve(workspaceRootDir);
73
- return [
74
- { operations: ["read"], paths: [`${escapeGlobLiteral(canonicalRoot)}/**`] },
75
- { operations: ["read"], paths: ["/**"], mode: "deny" },
76
- { operations: ["write"], paths: ["/**"], mode: "deny" },
77
- ];
48
+ export function buildPlanModePermissions() {
49
+ return [{ operations: ["write"], paths: ["/**"], mode: "deny" }];
78
50
  }
79
51
  //# sourceMappingURL=plan-mode-permissions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan-mode-permissions.js","sourceRoot":"","sources":["../../src/shared/plan-mode-permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CACtC,gBAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAChD,OAAO;QACL,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,iBAAiB,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;QAC3E,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;QACtD,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;KACxD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"plan-mode-permissions.js","sourceRoot":"","sources":["../../src/shared/plan-mode-permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Skill mount mechanics — the ONE cache shape shared by both harnesses.
3
+ *
4
+ * A skill "mount" is the on-disk materialization of a skill inside the
5
+ * session's platform directory: `{platformDir}/skills/{name}/` holding
6
+ * SKILL.md plus the artifact's supporting files. Both harnesses write to
7
+ * that same physical location — the Cursor harness directly, the native
8
+ * (deep-agent) harness through the `.stigmer/` virtual namespace that
9
+ * LocalWorkspaceBackend routes there — so they MUST share one cache shape
10
+ * (issues #337/#672). This module owns that shape:
11
+ *
12
+ * - The mount is cached by the skill's content-addressed
13
+ * `status.version_hash`: metadata is still fetched every execution
14
+ * (that keeps latest-version freshness — push a skill update and the
15
+ * very next message picks it up), but the artifact download and file
16
+ * rewrite are skipped when the mounted content's hash already matches.
17
+ * - Remounts rebuild the directory from scratch, so files deleted between
18
+ * skill versions never linger in the mount.
19
+ * - Crash-safe by ordering: the marker is stamped LAST, after every file
20
+ * of the mount landed — a crash mid-write leaves no marker, so the next
21
+ * execution remounts instead of trusting a partial tree.
22
+ *
23
+ * Extracted from execute-cursor/skill-resolver.ts (PR #682) when the
24
+ * deep-agent path adopted the same cache (issue #337). Orchestration —
25
+ * which skills to mount, prompt metadata, degradation logging — stays
26
+ * with each harness; only the per-skill-directory mechanics live here.
27
+ */
28
+ import type { StigmerClient } from "../client/stigmer-client.js";
29
+ import type { Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
30
+ /** Subdirectory of the platform dir where skill mounts live. */
31
+ export declare const SKILLS_SUBDIR = "skills";
32
+ /**
33
+ * Marker recording what a skill's mount directory currently holds. Written
34
+ * LAST, after every file of the mount landed — a crash mid-write leaves no
35
+ * marker, so the next execution remounts instead of trusting a partial tree.
36
+ */
37
+ export declare const MOUNT_MARKER_FILE = ".stigmer-mount.json";
38
+ export interface MountMarker {
39
+ /** Content-addressed version hash (`Skill.status.version_hash`) of the mounted content. */
40
+ versionHash: string;
41
+ /**
42
+ * Whether the artifact's files are part of the mount. `false` when the
43
+ * skill has no artifact OR when the download failed and the mount fell
44
+ * back to SKILL.md only — the latter makes the next execution retry the
45
+ * download rather than cache the degraded mount.
46
+ */
47
+ artifactMounted: boolean;
48
+ }
49
+ /**
50
+ * Whether the mount at `skillDir` already holds this version's content.
51
+ *
52
+ * Fresh means: the marker's hash matches AND the mount isn't a degraded
53
+ * SKILL.md-only fallback when the skill does carry an artifact. Any read or
54
+ * parse failure counts as stale — the remount is the safe default.
55
+ */
56
+ export declare function mountIsFresh(skillDir: string, versionHash: string, wantsArtifact: boolean): Promise<boolean>;
57
+ /**
58
+ * Download a skill artifact's ZIP bytes, transfer lane first (#675).
59
+ *
60
+ * The URL lane (getArtifactDownloadUrl → HTTP GET) carries any valid skill
61
+ * size; the unary getArtifact response is capped by the server's 10MB gRPC
62
+ * message limit. Servers that predate the lane (and cloud until its sibling
63
+ * lands) answer the mint with UNIMPLEMENTED — those fall back to the unary
64
+ * path, which behaves exactly as before for ≤10MB artifacts.
65
+ *
66
+ * Runs only on a mount-cache miss (the hash-keyed marker above) — a hit
67
+ * skips the transfer entirely, whichever lane would have carried it.
68
+ */
69
+ export declare function downloadArtifact(client: StigmerClient, artifactStorageKey: string): Promise<Uint8Array | undefined>;
70
+ /**
71
+ * (Re)write a skill's mount directory from scratch.
72
+ *
73
+ * The directory is removed first so files deleted between versions don't
74
+ * linger in the mount, then SKILL.md and the artifact files are written, and
75
+ * the marker is stamped LAST (see MOUNT_MARKER_FILE for the crash-safety
76
+ * contract). SKILL.md always comes from `spec.skillMd` — the server's
77
+ * authoritative copy, which the push pipeline guarantees is non-empty
78
+ * (push.go hard-fails a ZIP without an extractable SKILL.md) — never from
79
+ * the zip; both the zip's SKILL.md and any stray marker-named entry are
80
+ * excluded from extraction so the mount's ownership of those two files is
81
+ * unconditional.
82
+ *
83
+ * Every extracted entry must resolve inside `skillDir`. The server already
84
+ * rejects traversal at push (google/safearchive), but this runner-side
85
+ * check is kept as defense-in-depth: it is the guard the deep-agent path
86
+ * used to get from LocalWorkspaceBackend's platform-path routing, preserved
87
+ * here when skill writes moved to direct fs.
88
+ */
89
+ export declare function writeSkillMount(skill: Skill, skillDir: string, artifactBytes: Uint8Array | undefined): Promise<void>;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Skill mount mechanics — the ONE cache shape shared by both harnesses.
3
+ *
4
+ * A skill "mount" is the on-disk materialization of a skill inside the
5
+ * session's platform directory: `{platformDir}/skills/{name}/` holding
6
+ * SKILL.md plus the artifact's supporting files. Both harnesses write to
7
+ * that same physical location — the Cursor harness directly, the native
8
+ * (deep-agent) harness through the `.stigmer/` virtual namespace that
9
+ * LocalWorkspaceBackend routes there — so they MUST share one cache shape
10
+ * (issues #337/#672). This module owns that shape:
11
+ *
12
+ * - The mount is cached by the skill's content-addressed
13
+ * `status.version_hash`: metadata is still fetched every execution
14
+ * (that keeps latest-version freshness — push a skill update and the
15
+ * very next message picks it up), but the artifact download and file
16
+ * rewrite are skipped when the mounted content's hash already matches.
17
+ * - Remounts rebuild the directory from scratch, so files deleted between
18
+ * skill versions never linger in the mount.
19
+ * - Crash-safe by ordering: the marker is stamped LAST, after every file
20
+ * of the mount landed — a crash mid-write leaves no marker, so the next
21
+ * execution remounts instead of trusting a partial tree.
22
+ *
23
+ * Extracted from execute-cursor/skill-resolver.ts (PR #682) when the
24
+ * deep-agent path adopted the same cache (issue #337). Orchestration —
25
+ * which skills to mount, prompt metadata, degradation logging — stays
26
+ * with each harness; only the per-skill-directory mechanics live here.
27
+ */
28
+ import { mkdir, readFile, writeFile, rm } from "node:fs/promises";
29
+ import { join, dirname, extname, resolve } from "node:path";
30
+ import { ConnectError, Code } from "@connectrpc/connect";
31
+ import { extractZipFileEntries } from "./zip-extract.js";
32
+ /** Subdirectory of the platform dir where skill mounts live. */
33
+ export const SKILLS_SUBDIR = "skills";
34
+ /**
35
+ * Marker recording what a skill's mount directory currently holds. Written
36
+ * LAST, after every file of the mount landed — a crash mid-write leaves no
37
+ * marker, so the next execution remounts instead of trusting a partial tree.
38
+ */
39
+ export const MOUNT_MARKER_FILE = ".stigmer-mount.json";
40
+ /**
41
+ * File extensions written with the executable bit set. The ZIP layer is
42
+ * deliberately mode-blind (path + bytes only, see zip-extract.ts), so
43
+ * extension is the only signal available for `./script.sh`-style
44
+ * invocation to work out of the mount.
45
+ */
46
+ const SCRIPT_EXTENSIONS = new Set([".sh", ".py", ".js", ".ts", ".rb", ".pl"]);
47
+ /**
48
+ * Whether the mount at `skillDir` already holds this version's content.
49
+ *
50
+ * Fresh means: the marker's hash matches AND the mount isn't a degraded
51
+ * SKILL.md-only fallback when the skill does carry an artifact. Any read or
52
+ * parse failure counts as stale — the remount is the safe default.
53
+ */
54
+ export async function mountIsFresh(skillDir, versionHash, wantsArtifact) {
55
+ try {
56
+ const raw = await readFile(join(skillDir, MOUNT_MARKER_FILE), "utf-8");
57
+ const marker = JSON.parse(raw);
58
+ return marker.versionHash === versionHash && (marker.artifactMounted === true || !wantsArtifact);
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Download a skill artifact's ZIP bytes, transfer lane first (#675).
66
+ *
67
+ * The URL lane (getArtifactDownloadUrl → HTTP GET) carries any valid skill
68
+ * size; the unary getArtifact response is capped by the server's 10MB gRPC
69
+ * message limit. Servers that predate the lane (and cloud until its sibling
70
+ * lands) answer the mint with UNIMPLEMENTED — those fall back to the unary
71
+ * path, which behaves exactly as before for ≤10MB artifacts.
72
+ *
73
+ * Runs only on a mount-cache miss (the hash-keyed marker above) — a hit
74
+ * skips the transfer entirely, whichever lane would have carried it.
75
+ */
76
+ export async function downloadArtifact(client, artifactStorageKey) {
77
+ let minted;
78
+ try {
79
+ minted = await client.getSkillArtifactDownloadUrl(artifactStorageKey);
80
+ }
81
+ catch (err) {
82
+ if (err instanceof ConnectError && err.code === Code.Unimplemented) {
83
+ const resp = await client.getSkillArtifact(artifactStorageKey);
84
+ return resp.artifact && resp.artifact.length > 0 ? resp.artifact : undefined;
85
+ }
86
+ throw err;
87
+ }
88
+ const resp = await fetch(minted.url);
89
+ if (!resp.ok) {
90
+ throw new Error(`artifact fetch failed: HTTP ${resp.status} from ${minted.url}`);
91
+ }
92
+ const bytes = new Uint8Array(await resp.arrayBuffer());
93
+ if (minted.sizeBytes > 0n && BigInt(bytes.length) !== minted.sizeBytes) {
94
+ throw new Error(`artifact fetch truncated: got ${bytes.length} bytes, expected ${minted.sizeBytes}`);
95
+ }
96
+ return bytes.length > 0 ? bytes : undefined;
97
+ }
98
+ /**
99
+ * (Re)write a skill's mount directory from scratch.
100
+ *
101
+ * The directory is removed first so files deleted between versions don't
102
+ * linger in the mount, then SKILL.md and the artifact files are written, and
103
+ * the marker is stamped LAST (see MOUNT_MARKER_FILE for the crash-safety
104
+ * contract). SKILL.md always comes from `spec.skillMd` — the server's
105
+ * authoritative copy, which the push pipeline guarantees is non-empty
106
+ * (push.go hard-fails a ZIP without an extractable SKILL.md) — never from
107
+ * the zip; both the zip's SKILL.md and any stray marker-named entry are
108
+ * excluded from extraction so the mount's ownership of those two files is
109
+ * unconditional.
110
+ *
111
+ * Every extracted entry must resolve inside `skillDir`. The server already
112
+ * rejects traversal at push (google/safearchive), but this runner-side
113
+ * check is kept as defense-in-depth: it is the guard the deep-agent path
114
+ * used to get from LocalWorkspaceBackend's platform-path routing, preserved
115
+ * here when skill writes moved to direct fs.
116
+ */
117
+ export async function writeSkillMount(skill, skillDir, artifactBytes) {
118
+ await rm(skillDir, { recursive: true, force: true });
119
+ await mkdir(skillDir, { recursive: true });
120
+ await writeFile(join(skillDir, "SKILL.md"), skill.spec.skillMd, "utf-8");
121
+ const artifactMounted = artifactBytes !== undefined && artifactBytes.length > 0;
122
+ if (artifactMounted) {
123
+ const normalizedSkillDir = resolve(skillDir);
124
+ const entries = await extractZipFileEntries(artifactBytes, { exclude: ["SKILL.md", MOUNT_MARKER_FILE] });
125
+ for (const entry of entries) {
126
+ const filePath = resolve(normalizedSkillDir, entry.path);
127
+ if (filePath !== normalizedSkillDir && !filePath.startsWith(normalizedSkillDir + "/")) {
128
+ throw new Error(`skill artifact entry escapes its mount directory: '${entry.path}'`);
129
+ }
130
+ await mkdir(dirname(filePath), { recursive: true });
131
+ await writeFile(filePath, entry.content, {
132
+ mode: SCRIPT_EXTENSIONS.has(extname(entry.path)) ? 0o755 : 0o644,
133
+ });
134
+ }
135
+ }
136
+ const versionHash = skill.status?.versionHash ?? "";
137
+ if (versionHash !== "") {
138
+ const marker = { versionHash, artifactMounted };
139
+ await writeFile(join(skillDir, MOUNT_MARKER_FILE), JSON.stringify(marker), "utf-8");
140
+ }
141
+ }
142
+ //# sourceMappingURL=skill-mount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-mount.js","sourceRoot":"","sources":["../../src/shared/skill-mount.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Skill writing and prompt generation for the deep-agent execution path.
2
+ * Skill mounting and prompt generation for the deep-agent execution path.
3
3
  *
4
4
  * Follows the Agent Skills specification progressive disclosure model:
5
5
  *
@@ -10,11 +10,18 @@
10
10
  * 3. Resources (on demand) — scripts, references, and assets are
11
11
  * loaded by the agent only when required.
12
12
  *
13
- * Skills live under `.stigmer/skills/{name}/` relative to the workspace root.
14
- * Returned paths are workspace-relative so that the agent's sandbox backend
15
- * resolves them correctly regardless of mount strategy.
13
+ * Skills physically live in the session's platform directory
14
+ * (`{platformDir}/skills/{name}/` the SAME location the Cursor harness
15
+ * mounts into), and the agent sees them as `.stigmer/skills/{name}/`
16
+ * through the per-turn workspace symlink (see workspace/stigmer-link.ts).
17
+ * Returned paths are the agent-visible `.stigmer/…` form.
18
+ *
19
+ * Mounts are cached by the skill's content-addressed version hash via the
20
+ * shared skill-mount mechanics (issue #337, mirroring the Cursor harness's
21
+ * #672 fix): metadata is fetched every execution — a pushed skill update
22
+ * still lands on the very next message — but an unchanged skill skips the
23
+ * artifact download and rewrite entirely.
16
24
  */
17
- import type { WorkspaceBackend } from "./workspace/types.js";
18
25
  import type { Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
19
26
  import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
20
27
  import type { StigmerClient } from "../client/stigmer-client.js";
@@ -33,24 +40,24 @@ export declare function mergeSkillRefs(agentRefs: readonly ApiResourceReference[
33
40
  */
34
41
  export declare function fetchSkillsByRefs(client: StigmerClient, refs: readonly ApiResourceReference[]): Promise<Skill[]>;
35
42
  /**
36
- * Write skill artifacts to the workspace. Returns a map of
37
- * skill-id -> workspace-relative directory path.
43
+ * Mount skills into the session's platform directory, downloading each
44
+ * skill's artifact only on a version-hash cache miss (see skill-mount.ts
45
+ * for the marker mechanics). Returns a map of skill-id -> agent-visible
46
+ * directory path.
38
47
  *
39
- * For each skill:
40
- * - If a ZIP artifact is available, extract it to .stigmer/skills/{name}/
41
- * - Otherwise, write just the SKILL.md from the spec
42
- * - Make scripts executable
43
- */
44
- export declare function writeSkills(skills: readonly Skill[], workspaceBackend: WorkspaceBackend, artifacts: ReadonlyMap<string, Uint8Array>): Promise<SkillPathMap>;
45
- /**
46
- * Compute skill paths without writing anything (for resume integrity checks).
47
- */
48
- export declare function computeSkillPaths(skills: readonly Skill[]): Map<string, string>;
49
- /**
50
- * Check workspace integrity for resume fast-path.
51
- * Returns true if the sentinel SKILL.md file exists for the first skill.
48
+ * The returned map is a NAMING function, not a success record: every skill
49
+ * gets its path entry so prompt generation stays total, while mount
50
+ * failures degrade that one skill (loud log, no throw) one broken skill
51
+ * must never kill the run.
52
+ *
53
+ * Skills mount in parallel (each owns an independent directory), EXCEPT
54
+ * when two skills resolve to the same directory name (same `spec.name`
55
+ * from different orgs): concurrent remove-and-rewrite passes on one
56
+ * directory would corrupt the mount, so only the first claimant mounts and
57
+ * the collision is logged loudly. (The sequential code this replaced
58
+ * silently let the last writer win — no better, just quieter.)
52
59
  */
53
- export declare function checkSkillIntegrity(skills: readonly Skill[], workspaceBackend: WorkspaceBackend): Promise<boolean>;
60
+ export declare function mountSkills(client: StigmerClient, skills: readonly Skill[], platformDir: string): Promise<SkillPathMap>;
54
61
  /**
55
62
  * Generate the system prompt section following the Agent Skills spec
56
63
  * progressive disclosure model.
@@ -66,8 +73,3 @@ export declare function generatePromptSection(skills: readonly Skill[], skillPat
66
73
  * they are needed mid-conversation.
67
74
  */
68
75
  export declare function generateAlsoAvailableSection(excludedNames: readonly string[]): string;
69
- /**
70
- * Download skill artifacts for all skills that have a storage key.
71
- * Returns a map from skill ID to artifact bytes.
72
- */
73
- export declare function fetchSkillArtifacts(client: StigmerClient, skills: readonly Skill[]): Promise<Map<string, Uint8Array>>;