@herbertgao/pi-subagents 0.19.0 → 0.22.0-local.2

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.
@@ -41,18 +41,36 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
41
41
  import { isAbsolute, join } from "node:path"
42
42
  import { getAgentDir } from "@earendil-works/pi-coding-agent"
43
43
  import { isSymlink, isUnsafeName, safeReadFile } from "../memory.js"
44
+ import {
45
+ isPackageResourceExcluded,
46
+ packageWorkflowDirs,
47
+ packageWorkflowFiles,
48
+ } from "../package-resources.js"
44
49
  import { hasMetaDeclaration } from "./meta.js"
45
50
  import { MAX_SCRIPT_LENGTH } from "./runtime.js"
46
51
 
47
52
  /** Extension a saved workflow file carries. */
48
53
  const WORKFLOW_EXTENSION = ".js"
49
54
 
50
- /** The roots a `name` is looked up in, highest priority first. */
55
+ /**
56
+ * The roots a `name` is looked up in, highest priority first.
57
+ *
58
+ * Directories declared by installed pi packages come last, so a project, shared
59
+ * workspace or personal script always wins the name. That is the same tier
60
+ * package-provided agents get, and the same tier pi gives package-provided
61
+ * skills — a package offers a workflow, it never takes a name away from you.
62
+ *
63
+ * The `packageWorkflows` gate and pi's project-trust answer are session state in
64
+ * package-resources.ts rather than parameters here, so a gate set in `/agents`
65
+ * reaches this without an options bag travelling through `resolveWorkflowScript`,
66
+ * `resolveWorkflowSource` and `readSavedWorkflow` purely to be consulted once.
67
+ */
51
68
  export function savedWorkflowRoots(cwd: string): string[] {
52
69
  return [
53
70
  join(cwd, ".pi", "workflows"),
54
71
  join(cwd, ".agents", "workflows"),
55
72
  join(getAgentDir(), "workflows"),
73
+ ...packageWorkflowDirs(cwd),
56
74
  ]
57
75
  }
58
76
 
@@ -82,6 +100,9 @@ export function readSavedWorkflow(name: string, cwd: string): SavedWorkflow {
82
100
  for (const root of roots) {
83
101
  if (isSymlink(root)) continue // reject a symlinked root entirely, as skill-loader does
84
102
  const path = join(root, `${trimmed}${WORKFLOW_EXTENSION}`)
103
+ // A `!` entry in the declaring package's manifest. Only ever true for a path
104
+ // under a package root, so the local roots pay one set lookup for it.
105
+ if (isPackageResourceExcluded(path, cwd, "workflows")) continue
85
106
  const script = safeReadFile(path)
86
107
  if (script === undefined) continue
87
108
  // Found the file, so stop looking — a shadowing name that turns out not to
@@ -97,6 +118,25 @@ export function readSavedWorkflow(name: string, cwd: string): SavedWorkflow {
97
118
  return { ok: true, script, path }
98
119
  }
99
120
 
121
+ // Last: a package that declared an individual file rather than a directory.
122
+ // It carries no root to look a name up in, so it is matched by name here
123
+ // instead — after every root, which keeps package scripts lowest-precedence.
124
+ const declared = packageWorkflowFiles(cwd).get(trimmed)
125
+ if (declared !== undefined) {
126
+ const script = safeReadFile(declared)
127
+ if (script !== undefined) {
128
+ if (!hasMetaDeclaration(script)) {
129
+ return {
130
+ ok: false,
131
+ message:
132
+ `"${declared}" is not a workflow script — it has no \`export const meta = { name, description }\` ` +
133
+ "declaration. Nothing was run.",
134
+ }
135
+ }
136
+ return { ok: true, script, path: declared }
137
+ }
138
+ }
139
+
100
140
  const known = listSavedWorkflows(cwd)
101
141
  return {
102
142
  ok: false,
@@ -158,9 +198,15 @@ export function listSavedWorkflows(cwd: string): string[] {
158
198
  if (!entry.endsWith(WORKFLOW_EXTENSION)) continue
159
199
  const name = entry.slice(0, -WORKFLOW_EXTENSION.length)
160
200
  if (isUnsafeName(name)) continue
161
- if (isWorkflowFile(join(root, entry))) names.add(name)
201
+ const path = join(root, entry)
202
+ if (isPackageResourceExcluded(path, cwd, "workflows")) continue
203
+ if (isWorkflowFile(path)) names.add(name)
162
204
  }
163
205
  }
206
+ for (const [name, path] of packageWorkflowFiles(cwd)) {
207
+ if (isUnsafeName(name)) continue
208
+ if (isWorkflowFile(path)) names.add(name)
209
+ }
164
210
  return [...names].sort()
165
211
  }
166
212