pi-landstrip 0.17.7 → 0.17.12

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 (2) hide show
  1. package/package.json +2 -2
  2. package/subagents.ts +49 -38
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.17.7",
3
+ "version": "0.17.12",
4
4
  "description": "Sandbox-aware Pi subagents and interactive command permissions",
5
5
  "keywords": [
6
6
  "landstrip",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@earendil-works/pi-tui": "^0.80.6",
41
- "@landstrip/landstrip": "^0.17.7",
41
+ "@landstrip/landstrip": "^0.17.12",
42
42
  "minimatch": "^10.2.5"
43
43
  },
44
44
  "devDependencies": {
package/subagents.ts CHANGED
@@ -69,54 +69,37 @@ export function isSupportedPiVersion(version: readonly number[]): boolean {
69
69
  );
70
70
  }
71
71
 
72
- // Resolve the running Pi package from the extension's own location. The
73
- // extension is loaded by the running Pi and imports
74
- // `@earendil-works/pi-coding-agent`, so the nearest `node_modules` copy
75
- // reachable from `packageDir` is the running Pi itself. Reading its
72
+ // Resolve the Pi package used by this extension import. Reading its
76
73
  // `package.json` instead of spawning `pi --version` avoids depending on
77
74
  // `process.argv[1]`, which is not the Pi CLI entry when Pi runs as an embedded
78
75
  // or extension host and would otherwise report the Node version instead.
79
76
  export function resolvePiPackage(): PiPackage | undefined {
80
77
  if (piPackageResolved) return cachedPiPackage;
81
78
  piPackageResolved = true;
79
+
80
+ try {
81
+ const entry = fileURLToPath(import.meta.resolve('@earendil-works/pi-coding-agent'));
82
+ let dir = dirname(entry);
83
+ for (;;) {
84
+ const pkgPath = join(dir, 'package.json');
85
+ if (existsSync(pkgPath)) {
86
+ const pkg = readPiPackage(pkgPath);
87
+ if (pkg) return pkg;
88
+ }
89
+ const parent = dirname(dir);
90
+ if (parent === dir) break;
91
+ dir = parent;
92
+ }
93
+ } catch {
94
+ // Fall back to package discovery for runtimes without import.meta.resolve.
95
+ }
96
+
82
97
  let dir = packageDir;
83
98
  for (;;) {
84
99
  const pkgPath = join(dir, 'node_modules', '@earendil-works', 'pi-coding-agent', 'package.json');
85
100
  if (existsSync(pkgPath)) {
86
- try {
87
- const value: unknown = JSON.parse(readFileSync(pkgPath, 'utf8'));
88
- if (
89
- isRecord(value) &&
90
- value.name === '@earendil-works/pi-coding-agent' &&
91
- typeof value.version === 'string'
92
- ) {
93
- const parts = value.version.split('.').map(Number);
94
- if (parts.length >= 3 && parts.slice(0, 3).every((n) => Number.isInteger(n))) {
95
- const version = parts.slice(0, 3) as [number, number, number];
96
- const binFile =
97
- typeof value.bin === 'string'
98
- ? value.bin
99
- : isRecord(value.bin) && typeof value.bin.pi === 'string'
100
- ? value.bin.pi
101
- : undefined;
102
- if (binFile) {
103
- const cliEntry = join(
104
- dir,
105
- 'node_modules',
106
- '@earendil-works',
107
- 'pi-coding-agent',
108
- binFile,
109
- );
110
- if (existsSync(cliEntry)) {
111
- cachedPiPackage = { cliEntry, version };
112
- return cachedPiPackage;
113
- }
114
- }
115
- }
116
- }
117
- } catch {
118
- // Malformed package.json; keep walking ancestors.
119
- }
101
+ const pkg = readPiPackage(pkgPath);
102
+ if (pkg) return pkg;
120
103
  }
121
104
  const parent = dirname(dir);
122
105
  if (parent === dir) return undefined;
@@ -124,6 +107,34 @@ export function resolvePiPackage(): PiPackage | undefined {
124
107
  }
125
108
  }
126
109
 
110
+ function readPiPackage(pkgPath: string): PiPackage | undefined {
111
+ try {
112
+ const value: unknown = JSON.parse(readFileSync(pkgPath, 'utf8'));
113
+ if (
114
+ !isRecord(value) ||
115
+ value.name !== '@earendil-works/pi-coding-agent' ||
116
+ typeof value.version !== 'string'
117
+ ) {
118
+ return undefined;
119
+ }
120
+ const parts = value.version.split('.').map(Number);
121
+ if (parts.length < 3 || !parts.slice(0, 3).every((n) => Number.isInteger(n))) return undefined;
122
+ const binFile =
123
+ typeof value.bin === 'string'
124
+ ? value.bin
125
+ : isRecord(value.bin) && typeof value.bin.pi === 'string'
126
+ ? value.bin.pi
127
+ : undefined;
128
+ if (!binFile) return undefined;
129
+ const cliEntry = join(dirname(pkgPath), binFile);
130
+ if (!existsSync(cliEntry)) return undefined;
131
+ cachedPiPackage = { cliEntry, version: parts.slice(0, 3) as [number, number, number] };
132
+ return cachedPiPackage;
133
+ } catch {
134
+ return undefined;
135
+ }
136
+ }
137
+
127
138
  const taskParameters = Type.Object({
128
139
  description: Type.String({ description: 'A short 3-5 word description of the task' }),
129
140
  prompt: Type.String({ description: 'The complete task for the subagent' }),