takomi 2.1.35 → 2.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/README.md +6 -3
- package/.pi/extensions/oauth-router/commands.ts +36 -35
- package/.pi/extensions/oauth-router/index.ts +8 -8
- package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +40 -7
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +534 -55
- package/.pi/extensions/takomi-context-manager/index.ts +14 -2
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +16 -4
- package/.pi/extensions/takomi-context-manager/policy-tools.ts +7 -0
- package/.pi/extensions/takomi-context-manager/prerequisite-gates.ts +2 -0
- package/.pi/extensions/takomi-context-manager/session-state.ts +160 -0
- package/.pi/extensions/takomi-context-manager/skill-registry.ts +120 -0
- package/.pi/extensions/takomi-context-manager/skill-tools.ts +26 -3
- package/.pi/extensions/takomi-context-manager/state.ts +11 -1
- package/.pi/extensions/takomi-context-manager/types.ts +9 -1
- package/.pi/extensions/takomi-runtime/command-text.ts +2 -1
- package/.pi/extensions/takomi-runtime/commands.ts +13 -1
- package/.pi/extensions/takomi-runtime/index.ts +68 -1
- package/.pi/extensions/takomi-runtime/ui.ts +3 -3
- package/.pi/extensions/takomi-subagents/index.ts +37 -4
- package/.pi/extensions/takomi-subagents/native-render.ts +84 -7
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +18 -3
- package/.pi/extensions/takomi-subagents/tool-runner.ts +52 -5
- package/README.md +14 -7
- package/assets/.agent/skills/exam-creator-skill/SKILL.md +182 -0
- package/assets/.agent/skills/exam-creator-skill/references/model-routing-policy.md +48 -0
- package/assets/.agent/skills/exam-creator-skill/references/workflow-rulebook.md +115 -0
- package/package.json +2 -2
- package/src/cli.js +134 -13
- package/src/doctor.js +7 -1
- package/src/harness.js +54 -32
- package/src/owned-tree.js +28 -0
- package/src/pi-harness.js +149 -24
- package/src/store.js +2 -0
package/src/owned-tree.js
CHANGED
|
@@ -84,3 +84,31 @@ export async function copyOwnedTree(src, dest, options = {}) {
|
|
|
84
84
|
},
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
export async function linkOwnedTree(src, dest) {
|
|
89
|
+
const resolvedSrc = path.resolve(src);
|
|
90
|
+
const stat = await fs.lstat(resolvedSrc);
|
|
91
|
+
const type = stat.isDirectory()
|
|
92
|
+
? (process.platform === 'win32' ? 'junction' : 'dir')
|
|
93
|
+
: 'file';
|
|
94
|
+
await fs.ensureDir(path.dirname(dest));
|
|
95
|
+
await fs.symlink(resolvedSrc, dest, type);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function materializeOwnedTree(src, dest, options = {}) {
|
|
99
|
+
const linkMode = options.linkMode || 'copy';
|
|
100
|
+
if (linkMode === 'symlink' || linkMode === 'auto') {
|
|
101
|
+
try {
|
|
102
|
+
await linkOwnedTree(src, dest);
|
|
103
|
+
return { method: 'symlink' };
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (linkMode === 'symlink') throw error;
|
|
106
|
+
await fs.remove(dest).catch(() => {});
|
|
107
|
+
await copyOwnedTree(src, dest, options.copyOptions || {});
|
|
108
|
+
return { method: 'copy', fallbackError: error.message };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await copyOwnedTree(src, dest, options.copyOptions || {});
|
|
113
|
+
return { method: 'copy' };
|
|
114
|
+
}
|
package/src/pi-harness.js
CHANGED
|
@@ -15,6 +15,28 @@ function getPathEntries() {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function commandExists(command) {
|
|
18
|
+
const pathEntries = getPathEntries();
|
|
19
|
+
|
|
20
|
+
// Avoid shelling out on the hot launch path. On Windows, `where pi` is often
|
|
21
|
+
// ~100ms by itself; a direct PATH scan is much cheaper and good enough for
|
|
22
|
+
// npm/cmd shims. Keep `where`/`which` as a fallback for edge cases.
|
|
23
|
+
const manualCandidates = process.platform === 'win32'
|
|
24
|
+
? (() => {
|
|
25
|
+
const hasExtension = /\.(cmd|exe|bat|com)$/i.test(command);
|
|
26
|
+
const extensions = hasExtension
|
|
27
|
+
? ['']
|
|
28
|
+
: (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
|
|
29
|
+
.split(';')
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.sort((a, b) => (a.toLowerCase() === '.cmd' ? -1 : b.toLowerCase() === '.cmd' ? 1 : 0));
|
|
32
|
+
return pathEntries.flatMap((entry) => extensions.map((extension) => path.join(entry, `${command}${extension.toLowerCase()}`)));
|
|
33
|
+
})()
|
|
34
|
+
: pathEntries.map((entry) => path.join(entry, command));
|
|
35
|
+
|
|
36
|
+
for (const candidate of manualCandidates) {
|
|
37
|
+
if (fs.existsSync(candidate)) return { found: true, path: candidate };
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
const probe = process.platform === 'win32'
|
|
19
41
|
? spawnSync('where', [command], { stdio: 'pipe', encoding: 'utf8' })
|
|
20
42
|
: spawnSync('which', [command], { stdio: 'pipe', encoding: 'utf8' });
|
|
@@ -27,11 +49,6 @@ function commandExists(command) {
|
|
|
27
49
|
return { found: true, path: preferred || matches[0] || null };
|
|
28
50
|
}
|
|
29
51
|
|
|
30
|
-
for (const entry of getPathEntries()) {
|
|
31
|
-
const candidate = path.join(entry, process.platform === 'win32' ? `${command}.cmd` : command);
|
|
32
|
-
if (fs.existsSync(candidate)) return { found: true, path: candidate };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
52
|
return { found: false, path: null };
|
|
36
53
|
}
|
|
37
54
|
|
|
@@ -102,6 +119,100 @@ function getGlobalNodeModulesRoot(home = HOME) {
|
|
|
102
119
|
return path.join(home, '.npm-global', 'lib', 'node_modules');
|
|
103
120
|
}
|
|
104
121
|
|
|
122
|
+
async function readJsonIfExists(filePath) {
|
|
123
|
+
try {
|
|
124
|
+
if (await fs.pathExists(filePath)) return await fs.readJson(filePath);
|
|
125
|
+
} catch {}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function writeJsonWithBackup(filePath, value, backupLabel = 'takomi-backup') {
|
|
130
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
131
|
+
if (await fs.pathExists(filePath)) {
|
|
132
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
133
|
+
await fs.copy(filePath, `${filePath}.${backupLabel}-${stamp}`);
|
|
134
|
+
}
|
|
135
|
+
await fs.writeJson(filePath, value, { spaces: 2 });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function removeRawPiSubagentsPackageEntries(settings) {
|
|
139
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) return { changed: false, settings };
|
|
140
|
+
if (!Array.isArray(settings.packages)) return { changed: false, settings };
|
|
141
|
+
const before = settings.packages.length;
|
|
142
|
+
const nextPackages = settings.packages.filter((entry) => entry !== 'npm:pi-subagents' && entry !== 'pi-subagents');
|
|
143
|
+
if (nextPackages.length === before) return { changed: false, settings };
|
|
144
|
+
return { changed: true, settings: { ...settings, packages: nextPackages } };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function inspectRawPiSubagentsActivation({ cwd = process.cwd(), home = HOME } = {}) {
|
|
148
|
+
const user = getPiGlobalTargets(home);
|
|
149
|
+
const project = getProjectPiTargets(cwd);
|
|
150
|
+
const legacyExtension = path.join(user.extensions, 'subagent');
|
|
151
|
+
const findings = [];
|
|
152
|
+
|
|
153
|
+
for (const [scope, filePath] of [['user', user.settings], ['project', project.settings]]) {
|
|
154
|
+
const settings = await readJsonIfExists(filePath);
|
|
155
|
+
const packages = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
156
|
+
if (packages.includes('npm:pi-subagents') || packages.includes('pi-subagents')) {
|
|
157
|
+
findings.push({ type: 'settings-package', scope, path: filePath });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (await fs.pathExists(path.join(legacyExtension, 'index.ts')) || await fs.pathExists(path.join(legacyExtension, 'index.js'))) {
|
|
162
|
+
findings.push({ type: 'legacy-extension', scope: 'user', path: legacyExtension });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { findings };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function disableRawPiSubagentsActivation({ cwd = process.cwd(), home = HOME } = {}) {
|
|
169
|
+
const user = getPiGlobalTargets(home);
|
|
170
|
+
const project = getProjectPiTargets(cwd);
|
|
171
|
+
const actions = [];
|
|
172
|
+
|
|
173
|
+
for (const [scope, filePath] of [['user', user.settings], ['project', project.settings]]) {
|
|
174
|
+
const settings = await readJsonIfExists(filePath);
|
|
175
|
+
const result = removeRawPiSubagentsPackageEntries(settings);
|
|
176
|
+
if (result.changed) {
|
|
177
|
+
await writeJsonWithBackup(filePath, result.settings, 'takomi-disable-raw-pi-subagents');
|
|
178
|
+
actions.push({ type: 'settings-package-removed', scope, path: filePath });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const legacyExtension = path.join(user.extensions, 'subagent');
|
|
183
|
+
if (await fs.pathExists(path.join(legacyExtension, 'index.ts')) || await fs.pathExists(path.join(legacyExtension, 'index.js'))) {
|
|
184
|
+
const disabledRoot = path.join(user.root, 'disabled-extensions');
|
|
185
|
+
await fs.ensureDir(disabledRoot);
|
|
186
|
+
let dest = path.join(disabledRoot, 'subagent');
|
|
187
|
+
if (await fs.pathExists(dest)) {
|
|
188
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
189
|
+
dest = path.join(disabledRoot, `subagent-${stamp}`);
|
|
190
|
+
}
|
|
191
|
+
await fs.move(legacyExtension, dest, { overwrite: false });
|
|
192
|
+
actions.push({ type: 'legacy-extension-moved', scope: 'user', path: legacyExtension, disabledPath: dest });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { actions };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function formatRawPiSubagentsFindings(findings = []) {
|
|
199
|
+
if (!findings.length) return 'No raw pi-subagents activation found.';
|
|
200
|
+
return findings.map((finding) => {
|
|
201
|
+
if (finding.type === 'settings-package') return `- ${finding.scope} settings package entry: ${finding.path}`;
|
|
202
|
+
if (finding.type === 'legacy-extension') return `- legacy raw subagent extension: ${finding.path}`;
|
|
203
|
+
return `- ${finding.path || JSON.stringify(finding)}`;
|
|
204
|
+
}).join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function formatRawPiSubagentsDisableActions(actions = []) {
|
|
208
|
+
if (!actions.length) return 'No raw pi-subagents activation changes were needed.';
|
|
209
|
+
return actions.map((action) => {
|
|
210
|
+
if (action.type === 'settings-package-removed') return `- removed raw pi-subagents package from ${action.scope} settings: ${action.path}`;
|
|
211
|
+
if (action.type === 'legacy-extension-moved') return `- moved raw subagent extension out of Pi extensions: ${action.path} -> ${action.disabledPath}`;
|
|
212
|
+
return `- ${action.path || JSON.stringify(action)}`;
|
|
213
|
+
}).join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
105
216
|
export async function inspectPiSubagentsDependency(home = HOME) {
|
|
106
217
|
const pkg = await getPackageJson();
|
|
107
218
|
const declaredVersion = pkg?.dependencies?.['pi-subagents'] || null;
|
|
@@ -140,12 +251,17 @@ export async function inspectPiSubagentsDependency(home = HOME) {
|
|
|
140
251
|
};
|
|
141
252
|
}
|
|
142
253
|
|
|
143
|
-
export async function detectPiCommand() {
|
|
254
|
+
export async function detectPiCommand(options = {}) {
|
|
255
|
+
const { includeVersion = true } = options;
|
|
144
256
|
const binary = commandExists('pi');
|
|
145
257
|
if (!binary.found) {
|
|
146
258
|
return { installed: false, path: null, version: null };
|
|
147
259
|
}
|
|
148
260
|
|
|
261
|
+
if (!includeVersion) {
|
|
262
|
+
return { installed: true, path: binary.path, version: null };
|
|
263
|
+
}
|
|
264
|
+
|
|
149
265
|
const versionProbe = process.platform === 'win32'
|
|
150
266
|
? spawnSync('powershell', ['-NoProfile', '-Command', `& '${(binary.path || 'pi').replace(/'/g, "''")}' --version`], { stdio: 'pipe', encoding: 'utf8' })
|
|
151
267
|
: spawnSync(binary.path || 'pi', ['--version'], { stdio: 'pipe', encoding: 'utf8' });
|
|
@@ -381,7 +497,7 @@ export async function updatePiManagedPackages({ timeoutMs } = {}) {
|
|
|
381
497
|
return { ok: true, changed: false, report: 'Skipped Pi extension package update because TAKOMI_SKIP_PI_PACKAGE_UPDATE=1.' };
|
|
382
498
|
}
|
|
383
499
|
|
|
384
|
-
const pi = await detectPiCommand();
|
|
500
|
+
const pi = await detectPiCommand({ includeVersion: false });
|
|
385
501
|
if (!pi.installed) return { ok: false, changed: false, report: 'Pi is not installed.' };
|
|
386
502
|
|
|
387
503
|
// Only reconcile Pi-managed extension/package entries here. Plain `pi update`
|
|
@@ -425,6 +541,7 @@ export async function inspectPiHarnessEnvironment(cwd = process.cwd()) {
|
|
|
425
541
|
const bundled = await inspectBundledPiAssets();
|
|
426
542
|
const installed = await inspectInstalledTakomiPiHarness();
|
|
427
543
|
const piSubagents = await inspectPiSubagentsDependency();
|
|
544
|
+
const rawPiSubagentsActivation = await inspectRawPiSubagentsActivation({ cwd });
|
|
428
545
|
const project = getProjectPiTargets(cwd);
|
|
429
546
|
|
|
430
547
|
return {
|
|
@@ -432,6 +549,7 @@ export async function inspectPiHarnessEnvironment(cwd = process.cwd()) {
|
|
|
432
549
|
bundled,
|
|
433
550
|
installed,
|
|
434
551
|
piSubagents,
|
|
552
|
+
rawPiSubagentsActivation,
|
|
435
553
|
project: {
|
|
436
554
|
targets: project,
|
|
437
555
|
settingsPresent: await fs.pathExists(project.settings),
|
|
@@ -458,14 +576,22 @@ function printFirstRunGuidance(reason) {
|
|
|
458
576
|
}
|
|
459
577
|
|
|
460
578
|
export async function launchTakomiHarness(cwd = process.cwd()) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
579
|
+
// Keep the common `takomi` path lean. A full environment inspection shells out
|
|
580
|
+
// to `pi --version`, which costs multiple seconds on Windows before Pi even
|
|
581
|
+
// starts. For launch we only need to know the command exists and the required
|
|
582
|
+
// global Takomi extensions are present; `takomi doctor` still performs the
|
|
583
|
+
// complete diagnostic check.
|
|
584
|
+
const [pi, installed] = await Promise.all([
|
|
585
|
+
detectPiCommand({ includeVersion: false }),
|
|
586
|
+
inspectInstalledTakomiPiHarness(),
|
|
587
|
+
]);
|
|
588
|
+
|
|
589
|
+
if (!pi.installed) {
|
|
464
590
|
printFirstRunGuidance('Pi is not installed yet.');
|
|
465
591
|
return 1;
|
|
466
592
|
}
|
|
467
593
|
|
|
468
|
-
if (!
|
|
594
|
+
if (!installed.runtimeInstalled || !installed.subagentsInstalled) {
|
|
469
595
|
printFirstRunGuidance('Takomi Pi harness is not fully installed yet.');
|
|
470
596
|
return 1;
|
|
471
597
|
}
|
|
@@ -473,22 +599,21 @@ export async function launchTakomiHarness(cwd = process.cwd()) {
|
|
|
473
599
|
const env = {
|
|
474
600
|
...process.env,
|
|
475
601
|
TAKOMI_HARNESS: '1',
|
|
602
|
+
// Avoid Pi's blocking startup version check on this wrapped launch path.
|
|
603
|
+
// Takomi runtime schedules its own UI-safe delayed check after session_start.
|
|
604
|
+
PI_SKIP_VERSION_CHECK: process.env.PI_SKIP_VERSION_CHECK ?? '1',
|
|
605
|
+
TAKOMI_DELAYED_PI_VERSION_CHECK: process.env.TAKOMI_DELAYED_PI_VERSION_CHECK ?? '1',
|
|
476
606
|
};
|
|
477
607
|
|
|
478
608
|
return await new Promise((resolve) => {
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
cwd,
|
|
488
|
-
stdio: 'inherit',
|
|
489
|
-
env,
|
|
490
|
-
shell: false,
|
|
491
|
-
});
|
|
609
|
+
const resolved = resolveCommandForSpawn(pi.path || 'pi', []);
|
|
610
|
+
const child = spawn(resolved.command, resolved.args, {
|
|
611
|
+
cwd,
|
|
612
|
+
stdio: 'inherit',
|
|
613
|
+
env,
|
|
614
|
+
shell: false,
|
|
615
|
+
windowsHide: false,
|
|
616
|
+
});
|
|
492
617
|
|
|
493
618
|
child.on('close', (code) => resolve(code ?? 0));
|
|
494
619
|
child.on('error', () => resolve(1));
|
package/src/store.js
CHANGED
|
@@ -17,6 +17,7 @@ function createDefaultManifest() {
|
|
|
17
17
|
createdAt: new Date().toISOString(),
|
|
18
18
|
updatedAt: new Date().toISOString(),
|
|
19
19
|
linkedHarnesses: [],
|
|
20
|
+
syncMode: 'copy',
|
|
20
21
|
installed: {
|
|
21
22
|
skills: [],
|
|
22
23
|
workflows: [],
|
|
@@ -38,6 +39,7 @@ function normalizeManifest(manifest) {
|
|
|
38
39
|
workflows: normalizeOwnedMap(manifest?.bundledOwned?.workflows),
|
|
39
40
|
};
|
|
40
41
|
normalized.harnessOwned = manifest?.harnessOwned || {};
|
|
42
|
+
normalized.syncMode = manifest?.syncMode || 'copy';
|
|
41
43
|
return normalized;
|
|
42
44
|
}
|
|
43
45
|
|