thoth-agents 0.5.1 → 0.7.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 (62) hide show
  1. package/README.md +88 -11
  2. package/dist/agents/prompt-dialects.d.ts +1 -0
  3. package/dist/{chunk-MRFCA3U2.js → chunk-3MHJGWS4.js} +2 -1
  4. package/dist/{chunk-V7HSGRMB.js → chunk-P3GDM5RH.js} +70 -174
  5. package/dist/{chunk-OW6I7ZUU.js → chunk-QOIZYJ5C.js} +322 -151
  6. package/dist/{chunk-VUNYH263.js → chunk-W5TSNGEE.js} +263 -44
  7. package/dist/cli/index.js +4 -4
  8. package/dist/cli/pi-install.d.ts +35 -67
  9. package/dist/cli/pi-tool-config.d.ts +19 -0
  10. package/dist/cli/tui/index.js +3 -3
  11. package/dist/config/constants.d.ts +30 -3
  12. package/dist/config/schema.d.ts +4 -0
  13. package/dist/harness/adapters/claude-code.d.ts +1 -1
  14. package/dist/harness/core/agent-pack.d.ts +6 -2
  15. package/dist/harness/core/result-contract.d.ts +366 -0
  16. package/dist/harness/core/sdd.d.ts +1 -1
  17. package/dist/harness/writers/pi-agent.d.ts +1 -0
  18. package/dist/index.js +2 -2
  19. package/dist/pi.d.ts +6 -35
  20. package/dist/pi.js +37 -433
  21. package/package.json +5 -3
  22. package/pi/.thoth-agents-assets.json +5 -5
  23. package/pi/agents/thoth-designer.md +15 -20
  24. package/pi/agents/thoth-explorer.md +15 -19
  25. package/pi/agents/thoth-librarian.md +15 -19
  26. package/pi/agents/thoth-oracle.md +30 -22
  27. package/pi/agents/thoth-worker.md +13 -18
  28. package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +6 -6
  29. package/plugin/.claude-plugin/plugin.json +1 -1
  30. package/plugin/.codex-plugin/plugin.json +1 -1
  31. package/plugin/agents/designer.md +9 -14
  32. package/plugin/agents/explorer.md +10 -14
  33. package/plugin/agents/librarian.md +9 -13
  34. package/plugin/agents/oracle.md +24 -18
  35. package/plugin/agents/orchestrator.md +38 -61
  36. package/plugin/agents/worker.md +9 -14
  37. package/plugin/skills/plan-reviewer/SKILL.md +16 -8
  38. package/plugin/skills/thoth-archive/SKILL.md +2 -1
  39. package/plugin/skills/thoth-constitution/SKILL.md +1 -1
  40. package/plugin/skills/thoth-constitution/templates/constitution.md +1 -1
  41. package/plugin/skills/thoth-init/SKILL.md +1 -1
  42. package/plugin/skills/thoth-sdd/SKILL.md +67 -2
  43. package/plugin/skills/thoth-sdd/references/phases/verify.md +11 -1
  44. package/plugin/skills/thoth-sdd/scripts/result-contract.json +210 -0
  45. package/plugin/skills/thoth-sdd/scripts/validate-result.d.mts +47 -0
  46. package/plugin/skills/thoth-sdd/scripts/validate-result.mjs +390 -0
  47. package/plugin/skills/thoth-sdd/scripts/validate.mjs +213 -8
  48. package/plugin/skills/thoth-sdd/templates/change.md +8 -0
  49. package/skills/plan-reviewer/SKILL.md +16 -8
  50. package/skills/thoth-archive/SKILL.md +2 -1
  51. package/skills/thoth-constitution/SKILL.md +1 -1
  52. package/skills/thoth-constitution/templates/constitution.md +1 -1
  53. package/skills/thoth-init/SKILL.md +1 -1
  54. package/skills/thoth-sdd/SKILL.md +67 -2
  55. package/skills/thoth-sdd/references/phases/verify.md +11 -1
  56. package/skills/thoth-sdd/scripts/result-contract.json +210 -0
  57. package/skills/thoth-sdd/scripts/validate-result.d.mts +47 -0
  58. package/skills/thoth-sdd/scripts/validate-result.mjs +390 -0
  59. package/skills/thoth-sdd/scripts/validate.mjs +213 -8
  60. package/skills/thoth-sdd/templates/change.md +8 -0
  61. package/thoth-agents.schema.json +14 -0
  62. package/dist/pi/tools-panel.d.ts +0 -51
@@ -11,6 +11,7 @@ export interface PiAgentDefinitionInput {
11
11
  instructions: string;
12
12
  model?: string;
13
13
  effort?: string;
14
+ skills?: readonly string[];
14
15
  subagentMode: 'task' | 'background';
15
16
  }
16
17
  export declare function getPiSpecialistDefaultTools(role: PiSpecialistRole): string[];
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  renderOpenCodeAgentConfigs,
4
4
  spawn,
5
5
  spawnSync
6
- } from "./chunk-MRFCA3U2.js";
6
+ } from "./chunk-3MHJGWS4.js";
7
7
  import {
8
8
  context7,
9
9
  exa,
@@ -14,7 +14,7 @@ import {
14
14
  getOpenCodeConfigPaths,
15
15
  loadPluginConfig,
16
16
  stripJsonComments
17
- } from "./chunk-OW6I7ZUU.js";
17
+ } from "./chunk-QOIZYJ5C.js";
18
18
 
19
19
  // src/index.ts
20
20
  import { fileURLToPath as fileURLToPath3 } from "url";
package/dist/pi.d.ts CHANGED
@@ -1,52 +1,23 @@
1
- import { type OverlayOptions } from '@earendil-works/pi-tui';
2
- import { readPiToolConfig, savePiToolConfig } from './cli/pi-tool-config';
3
- import { type ToolsPanelKey } from './pi/tools-panel';
4
1
  type PiHandler = (event: Record<string, unknown>, context?: {
5
- isIdle(): boolean;
6
- }) => unknown;
7
- interface PiCommandContext {
8
- mode: string;
9
- ui: {
2
+ isIdle?(): boolean;
3
+ ui?: {
10
4
  notify(message: string, type?: 'info' | 'warning' | 'error'): void;
11
- custom<T>(factory: (tui: {
12
- requestRender(): void;
13
- terminal: {
14
- rows: number;
15
- };
16
- }, theme: unknown, keybindings: unknown, done: (result: T) => void) => {
17
- render(width: number): string[];
18
- invalidate(): void;
19
- handleInput(data: string): void;
20
- }, options?: {
21
- overlay?: boolean;
22
- overlayOptions?: OverlayOptions;
23
- }): Promise<T>;
24
5
  };
25
- }
26
- interface PiNativeModules {
27
- matchesKey(data: string, key: string): boolean;
28
- truncateToWidth(text: string, width: number): string;
29
- visibleWidth(text: string): number;
30
- keys: Record<ToolsPanelKey, string>;
31
- }
6
+ }) => unknown;
32
7
  export interface PiExtensionApi {
33
8
  on(event: 'input' | 'before_agent_start' | 'session_start', handler: PiHandler): void;
34
9
  registerCommand?(name: string, command: {
35
10
  description: string;
36
- handler(args: string | undefined, context: PiCommandContext): unknown;
11
+ handler(args: string | undefined, context: unknown): unknown;
37
12
  }): void;
38
- getAllTools?(): Array<{
13
+ getCommands?(): Array<{
39
14
  name: string;
40
- description?: string;
15
+ source?: string;
41
16
  }>;
42
- getActiveTools?(): string[];
43
17
  }
44
18
  export interface PiExtensionOptions {
45
19
  packageRoot?: string;
46
20
  piRoot?: string;
47
- readToolConfig?: typeof readPiToolConfig;
48
- saveToolConfig?: typeof savePiToolConfig;
49
- loadNativeModules?: () => Promise<PiNativeModules>;
50
21
  }
51
22
  export declare function injectPiRoot(systemPrompt: string): string;
52
23
  export default function thothAgentsPiExtension(pi: PiExtensionApi, options?: PiExtensionOptions): void;
package/dist/pi.js CHANGED
@@ -1,371 +1,42 @@
1
1
  import {
2
2
  PI_ROOT_END,
3
3
  PI_ROOT_START,
4
- readPiToolConfig,
4
+ createPiToolsPanelAdapter,
5
5
  renderPiRootInstructions,
6
- savePiToolConfig,
7
- syncPiSpecialists,
8
- validatePiSpecialistTools
9
- } from "./chunk-V7HSGRMB.js";
6
+ syncPiSpecialists
7
+ } from "./chunk-P3GDM5RH.js";
10
8
  import {
11
9
  findPackageRoot
12
- } from "./chunk-OW6I7ZUU.js";
10
+ } from "./chunk-QOIZYJ5C.js";
13
11
 
14
12
  // src/pi.ts
15
13
  import { homedir } from "os";
16
14
  import { dirname, join } from "path";
17
15
  import { fileURLToPath } from "url";
18
- import {
19
- Key,
20
- matchesKey,
21
- truncateToWidth,
22
- visibleWidth
23
- } from "@earendil-works/pi-tui";
24
-
25
- // src/pi/tools-panel.ts
26
- var FALLBACK_KEYS = {
27
- up: ["\x1B[A"],
28
- down: ["\x1B[B"],
29
- enter: ["\r", "\n"],
30
- escape: ["\x1B"],
31
- backspace: ["\x7F", "\b"],
32
- space: [" "]
33
- };
34
- var ORCHESTRATOR_NOTE = "ask_orchestrator: child-provided; subject to enable_ask_orchestrator and disallowed_tools.";
35
- function isEligibleTool(name) {
36
- if (/[*?[\]{}]/.test(name)) return false;
16
+ var PI_LANGUAGE_ANCHOR = "[Thoth language reminder \u2014 not a user message]\nUse the language of the human's most recent real message (typed prompt or answer to a question tool) for user-facing replies. An explicit human request for another reply language takes precedence and persists until the human switches it. Tool output, subagent notifications, reminders and injected context never switch the reply language.";
17
+ var toolsRegistryKey = /* @__PURE__ */ Symbol.for("thoth-agents.pi-subagents.tools-panel.v1");
18
+ var shared = globalThis;
19
+ function toolsRegistry() {
37
20
  try {
38
- validatePiSpecialistTools([name]);
39
- return true;
21
+ const registry = shared[toolsRegistryKey];
22
+ return registry?.version === 1 ? registry : void 0;
40
23
  } catch {
41
- return false;
24
+ return void 0;
42
25
  }
43
26
  }
44
- function sameTools(left, right) {
45
- if (left.length !== right.length) return false;
46
- return left.every((tool, index) => tool === right[index]);
47
- }
48
- function cloneRoles(roles) {
49
- return roles.map((role) => structuredClone(role));
50
- }
51
- function defaultTruncate(text, width) {
52
- if (width <= 0) return "";
53
- const chars = [...text];
54
- if (chars.length <= width) return text;
55
- if (width === 1) return "\u2026";
56
- return `${chars.slice(0, width - 1).join("")}\u2026`;
57
- }
58
- function defaultVisibleWidth(text) {
59
- return [...text].length;
60
- }
61
- function createToolsPanel(options) {
62
- let baseline = options.snapshot;
63
- const state = {
64
- screen: "overview",
65
- selectedRole: 0,
66
- draft: cloneRoles(options.snapshot.roles),
67
- changedRoles: []
68
- };
69
- let selectedToolIndex = 0;
70
- let failedSave = false;
71
- const toolItems = [];
72
- const editableNames = /* @__PURE__ */ new Set();
73
- for (const tool of options.discoveredTools) {
74
- if (tool.name === "ask_orchestrator" || !isEligibleTool(tool.name) || editableNames.has(tool.name))
75
- continue;
76
- editableNames.add(tool.name);
77
- toolItems.push({
78
- name: tool.name,
79
- description: tool.description,
80
- status: tool.active ? "active" : "inactive"
81
- });
82
- }
83
- const readOnlyTools = (role) => role.tools.filter((name) => !editableNames.has(name));
84
- const readOnlyLine = (role) => {
85
- const names = readOnlyTools(role);
86
- return names.length ? [`Read-only: ${names.join(", ")} (edit manually in the definition)`] : [];
87
- };
88
- const isKey = (data, key) => key === "escape" && data === "" || (options.matchesKey?.(data, key) ?? FALLBACK_KEYS[key].includes(data));
89
- const requestRender = () => options.requestRender?.();
90
- const dirty = () => state.draft.some((role) => {
91
- const original = baseline.roles.find((item) => item.role === role.role);
92
- return !original || !sameTools(role.tools, original.tools);
93
- });
94
- const selectionLabel = (role) => `${role.tools.length} selected`;
95
- const selectionSummary = (role) => role.tools.join(", ") || "(none)";
96
- const save = () => {
97
- if (!dirty() && !failedSave) {
98
- options.onDone({ kind: "saved", changedRoles: [...state.changedRoles] });
99
- return;
100
- }
101
- state.error = void 0;
102
- const result = options.save(baseline, cloneRoles(state.draft));
103
- baseline = result.snapshot;
104
- state.changedRoles = [
105
- .../* @__PURE__ */ new Set([...state.changedRoles, ...result.changedRoles])
106
- ];
107
- failedSave = !result.success;
108
- if (result.success) {
109
- options.onDone({ kind: "saved", changedRoles: [...state.changedRoles] });
110
- return;
111
- }
112
- state.error = result.error ?? "Saving global specialist tools failed.";
113
- requestRender();
114
- };
115
- const openSelectedRole = () => {
116
- state.screen = "tools";
117
- selectedToolIndex = 0;
118
- };
119
- const restoreDefaultsForRole = (role) => {
120
- role.tools = [...role.defaultTools];
121
- };
122
- const toggleCurrentTool = () => {
123
- const role = state.draft[state.selectedRole];
124
- if (!role) return;
125
- const item = toolItems[selectedToolIndex];
126
- if (!item) return;
127
- const currentTools = role.tools;
128
- if (currentTools.includes(item.name)) {
129
- role.tools = currentTools.filter((t) => t !== item.name);
130
- } else {
131
- role.tools = [...currentTools, item.name];
132
- }
133
- };
134
- const handleOverview = (data) => {
135
- if (isKey(data, "up") || data === "k") {
136
- state.selectedRole = Math.max(0, state.selectedRole - 1);
137
- } else if (isKey(data, "down") || data === "j") {
138
- state.selectedRole = Math.min(
139
- state.draft.length - 1,
140
- state.selectedRole + 1
141
- );
142
- } else if (data === "g") {
143
- state.selectedRole = 0;
144
- } else if (data === "G") {
145
- state.selectedRole = Math.max(0, state.draft.length - 1);
146
- } else if (isKey(data, "enter") || data === "e") {
147
- openSelectedRole();
148
- } else if (isKey(data, "escape") || data === "q") {
149
- if (dirty() || failedSave) state.screen = "discard";
150
- else options.onDone({ kind: "cancelled" });
151
- } else if (data.toLowerCase() === "s") {
152
- save();
153
- } else if (data.toLowerCase() === "r") {
154
- const role = state.draft[state.selectedRole];
155
- if (role) restoreDefaultsForRole(role);
156
- }
157
- };
158
- const handleTools = (data) => {
159
- const role = state.draft[state.selectedRole];
160
- if (!role) return;
161
- const items = toolItems;
162
- if (isKey(data, "up") || data === "k") {
163
- if (items.length > 0) {
164
- selectedToolIndex = (selectedToolIndex - 1 + items.length) % items.length;
165
- }
166
- } else if (isKey(data, "down") || data === "j") {
167
- if (items.length > 0) {
168
- selectedToolIndex = (selectedToolIndex + 1) % items.length;
169
- }
170
- } else if (data === "g") {
171
- selectedToolIndex = 0;
172
- } else if (data === "G") {
173
- selectedToolIndex = Math.max(0, items.length - 1);
174
- } else if (isKey(data, "space") || data === " ") {
175
- toggleCurrentTool();
176
- } else if (data.toLowerCase() === "r") {
177
- restoreDefaultsForRole(role);
178
- } else if (isKey(data, "enter") || isKey(data, "escape") || data === "q") {
179
- state.screen = "overview";
180
- }
181
- };
182
- const handleDiscard = (data) => {
183
- if (data.toLowerCase() === "d") options.onDone({ kind: "cancelled" });
184
- else if (isKey(data, "escape") || data.toLowerCase() === "k")
185
- state.screen = "overview";
186
- };
187
- const handleInput = (data) => {
188
- if (state.screen === "overview") handleOverview(data);
189
- else if (state.screen === "tools") handleTools(data);
190
- else handleDiscard(data);
191
- requestRender();
192
- };
193
- const fitChoiceViewport = (lines, choices, cursor, height) => {
194
- if (lines.length <= height) return lines;
195
- if (height <= 0) return [];
196
- const messages = lines.filter(
197
- (line) => line.startsWith("Save failed:") || line.startsWith("Already changed:")
198
- ).slice(0, Math.max(0, height - 1));
199
- const headerSpace = height - messages.length - 1;
200
- const header = headerSpace >= 2 ? lines.slice(0, 2) : headerSpace === 1 ? lines.slice(1, 2) : [];
201
- const available = height - header.length - messages.length;
202
- const showRange = choices.length > available && available > 1;
203
- const pageSize = available - Number(showRange);
204
- const start = Math.max(
205
- 0,
206
- Math.min(cursor - Math.floor(pageSize / 2), choices.length - pageSize)
207
- );
208
- return [
209
- ...header,
210
- ...messages,
211
- ...choices.slice(start, start + pageSize),
212
- ...showRange ? [` Showing ${start + 1}\u2013${start + pageSize} of ${choices.length}`] : []
213
- ];
214
- };
215
- const overviewLines = (width, height) => {
216
- const dirtyCount = state.draft.filter((role) => {
217
- const original = baseline.roles.find((item) => item.role === role.role);
218
- return !original || !sameTools(role.tools, original.tools);
219
- }).length;
220
- const lines = [
221
- `target: global specialist definitions \xB7 ${dirtyCount ? `pending: ${dirtyCount} change${dirtyCount === 1 ? "" : "s"}` : "pending: none"}`,
222
- "\u2191/\u2193/j/k move \xB7 enter/e edit \xB7 r defaults \xB7 s save \xB7 esc/q cancel",
223
- ""
224
- ];
225
- const innerWidth = Math.max(1, width - 4);
226
- const table = innerWidth >= 80;
227
- if (table)
228
- lines.push(
229
- `${"agent".padEnd(18)} ${"selected tools".padEnd(18)} tools`
230
- );
231
- else lines.push("agent \xB7 selected tools");
232
- const choices = [];
233
- for (const [index, role] of state.draft.entries()) {
234
- const marker = index === state.selectedRole ? "\u203A" : " ";
235
- const changed = (() => {
236
- const original = baseline.roles.find((item) => item.role === role.role);
237
- return original && sameTools(role.tools, original.tools) ? "" : " *";
238
- })();
239
- const toolSummary = selectionSummary(role);
240
- const countLabel = selectionLabel(role);
241
- const name = `${role.role}${changed}`;
242
- choices.push(
243
- table ? `${marker} ${name.padEnd(16)} ${countLabel.padEnd(18)} ${toolSummary}` : `${marker} ${name} \xB7 ${countLabel} \xB7 ${toolSummary}`
244
- );
245
- }
246
- lines.push(...choices, "");
247
- const selected = state.draft[state.selectedRole];
248
- lines.push(
249
- `selected: ${selected?.role ?? "(none)"} \xB7 tools: ${selected ? selectionSummary(selected) : "(none)"}`
250
- );
251
- lines.push(`Global directory: ${baseline.piRoot}`);
252
- lines.push("Ambient root tools are unchanged.");
253
- lines.push(
254
- "Native settings or project definitions may override these global definitions."
255
- );
256
- lines.push("Child specialists do not inherit root tools automatically.");
257
- lines.push(ORCHESTRATOR_NOTE);
258
- if (selected) lines.push(...readOnlyLine(selected));
259
- if (state.error) lines.push(`Save failed: ${state.error}`);
260
- if (state.changedRoles.length > 0)
261
- lines.push(`Already changed: ${state.changedRoles.join(", ")}`);
262
- return fitChoiceViewport(lines, choices, state.selectedRole, height);
263
- };
264
- const toolLines = (height) => {
265
- const role = state.draft[state.selectedRole];
266
- if (!role) return [];
267
- const items = toolItems;
268
- const selectedSet = new Set(role.tools);
269
- const choices = items.map((item, index) => {
270
- const isCursor = index === selectedToolIndex;
271
- const marker = isCursor ? "\u203A" : " ";
272
- const checked = selectedSet.has(item.name) ? "[x]" : "[ ]";
273
- const badge = item.status === "inactive" ? " (inactive)" : "";
274
- const desc = item.description ? ` \xB7 ${item.description}` : "";
275
- return `${marker} ${checked} ${item.name}${badge}${desc}`;
276
- });
277
- const pageSize = 10;
278
- const start = Math.max(
279
- 0,
280
- Math.min(
281
- selectedToolIndex - Math.floor(pageSize / 2),
282
- choices.length - pageSize
283
- )
284
- );
285
- const lines = [
286
- `row: ${role.role} \xB7 ${selectionLabel(role)}`,
287
- "\u2191/\u2193/j/k move \xB7 space toggle \xB7 r defaults \xB7 enter/esc/q back",
288
- ORCHESTRATOR_NOTE,
289
- ...readOnlyLine(role),
290
- "",
291
- ...choices.slice(start, start + pageSize)
292
- ];
293
- if (items.length === 0) lines.push(" No tools available");
294
- else if (choices.length > pageSize)
295
- lines.push(
296
- ` Showing ${start + 1}\u2013${Math.min(start + pageSize, choices.length)} of ${choices.length}`
297
- );
298
- lines.push("", `selected: ${items[selectedToolIndex]?.name ?? "(none)"}`);
299
- return fitChoiceViewport(
300
- lines,
301
- choices.length ? choices : [" No tools available"],
302
- selectedToolIndex,
303
- height
304
- );
305
- };
306
- const render = (width) => {
307
- const maxHeight = Math.max(
308
- 1,
309
- Math.floor(options.maxHeight?.() ?? Infinity)
310
- );
311
- const bodyHeight = Math.max(0, maxHeight - 2);
312
- let title;
313
- let lines;
314
- if (state.screen === "overview") {
315
- title = "Global specialist tools";
316
- lines = overviewLines(width, bodyHeight);
317
- } else if (state.screen === "tools") {
318
- const role = state.draft[state.selectedRole];
319
- title = `Choose tools \xB7 ${role?.role ?? ""}`;
320
- lines = toolLines(bodyHeight);
321
- } else {
322
- title = "Discard unsaved draft?";
323
- lines = ["d discard and close \xB7 k or esc keep editing"];
324
- }
325
- const truncate = options.truncate ?? defaultTruncate;
326
- const measure = options.visibleWidth ?? defaultVisibleWidth;
327
- const fg = (color, text) => options.theme?.fg(color, text) ?? text;
328
- const panelWidth = Math.max(1, width);
329
- if (maxHeight < 3) return [truncate(title, panelWidth)];
330
- if (panelWidth < 4) {
331
- return [title, ...lines].map((line) => truncate(line, panelWidth));
332
- }
333
- const innerWidth = panelWidth - 4;
334
- const titleText = truncate(` ${title} `, panelWidth - 2);
335
- const top = fg(
336
- "accent",
337
- `\u256D${titleText}${"\u2500".repeat(Math.max(0, panelWidth - 2 - measure(titleText)))}\u256E`
27
+ function hasToolsOwnership(pi) {
28
+ const capability = toolsRegistry()?.capability;
29
+ if (capability?.version !== 1 || capability.command !== "subagents-tools")
30
+ return false;
31
+ if (typeof pi.getCommands !== "function") return true;
32
+ try {
33
+ return pi.getCommands().some(
34
+ (command) => (!command.source || command.source === "extension") && /^subagents-tools(?::\d+)?$/.test(command.name)
338
35
  );
339
- const bottom = fg("border", `\u2570${"\u2500".repeat(panelWidth - 2)}\u256F`);
340
- const body = lines.map((line) => {
341
- const selected = line.startsWith("\u203A ");
342
- let content = truncate(line, innerWidth);
343
- if (selected) content = fg("accent", content);
344
- else if (line === "" || line.includes("\u2191/\u2193/j/k") || line.startsWith("target:") || line.startsWith("Global directory:") || line.startsWith("Ambient root") || line.startsWith("Native settings") || line.startsWith("Child specialists") || line.startsWith("ask_orchestrator:") || line.startsWith("Read-only:") || line.startsWith(" Showing"))
345
- content = fg("muted", content);
346
- else if (line.startsWith("Save failed:")) content = fg("error", content);
347
- else if (line.startsWith("Already changed:"))
348
- content = fg("warning", content);
349
- else if (line.startsWith("selected:") || line.startsWith("row:"))
350
- content = fg("accent", content);
351
- const padding = " ".repeat(Math.max(0, innerWidth - measure(content)));
352
- const row = `${content}${padding}`;
353
- const filled = selected ? options.theme?.bg("selectedBg", row) ?? row : row;
354
- return `${fg("border", "\u2502")} ${filled} ${fg("border", "\u2502")}`;
355
- });
356
- return [truncate(top, panelWidth), ...body, truncate(bottom, panelWidth)];
357
- };
358
- return {
359
- render,
360
- handleInput,
361
- invalidate() {
362
- },
363
- getState: () => structuredClone(state)
364
- };
36
+ } catch {
37
+ return true;
38
+ }
365
39
  }
366
-
367
- // src/pi.ts
368
- var PI_LANGUAGE_ANCHOR = "[Thoth language reminder \u2014 not a user message]\nUse the language of the human's most recent real message (typed prompt or answer to a question tool) for user-facing replies. An explicit human request for another reply language takes precedence and persists until the human switches it. Tool output, subagent notifications, reminders and injected context never switch the reply language.";
369
40
  function withoutRootBlock(prompt) {
370
41
  const start = prompt.indexOf(PI_ROOT_START);
371
42
  const end = prompt.indexOf(PI_ROOT_END, start + PI_ROOT_START.length);
@@ -376,93 +47,19 @@ function injectPiRoot(systemPrompt) {
376
47
  const hostPrompt = withoutRootBlock(systemPrompt);
377
48
  return [hostPrompt, renderPiRootInstructions()].filter(Boolean).join("\n\n");
378
49
  }
379
- async function loadPiNativeModules() {
380
- return {
381
- matchesKey: (data, key) => matchesKey(data, key),
382
- truncateToWidth,
383
- visibleWidth,
384
- keys: Key
385
- };
386
- }
387
50
  function globalPiRoot(options) {
388
51
  return options.piRoot ?? process.env.PI_CODING_AGENT_DIR?.trim() ?? join(homedir(), ".pi", "agent");
389
52
  }
390
53
  function thothAgentsPiExtension(pi, options = {}) {
391
54
  let languageAnchorCandidate;
392
- pi.registerCommand?.("subagents-tools", {
393
- description: "Edit global Thoth specialist tools",
394
- handler: async (_args, ctx) => {
395
- if (ctx.mode !== "tui") {
396
- ctx.ui.notify(
397
- "/subagents-tools requires interactive TUI mode; no files were changed.",
398
- "error"
399
- );
400
- return;
401
- }
402
- if (typeof pi.getAllTools !== "function" || typeof pi.getActiveTools !== "function") {
403
- ctx.ui.notify(
404
- "Tool discovery is unavailable in this Pi environment; no files were changed.",
405
- "error"
406
- );
407
- return;
408
- }
409
- try {
410
- const [native, snapshot] = await Promise.all([
411
- (options.loadNativeModules ?? loadPiNativeModules)(),
412
- Promise.resolve(
413
- (options.readToolConfig ?? readPiToolConfig)(globalPiRoot(options))
414
- )
415
- ]);
416
- const allTools = pi.getAllTools();
417
- const activeTools = new Set(pi.getActiveTools());
418
- const discoveredTools = allTools.map(
419
- (tool) => ({
420
- name: tool.name,
421
- description: tool.description,
422
- active: activeTools.has(tool.name)
423
- })
424
- );
425
- const result = await ctx.ui.custom(
426
- (tui, theme, _keybindings, done) => createToolsPanel({
427
- snapshot,
428
- discoveredTools,
429
- save: (current, draft) => (options.saveToolConfig ?? savePiToolConfig)(current, draft),
430
- onDone: done,
431
- requestRender: () => tui.requestRender(),
432
- // Match the overlay's 90% cap on every render, including resize.
433
- maxHeight: () => Math.max(1, Math.floor(tui.terminal.rows * 90 / 100)),
434
- matchesKey: (data, key) => native.matchesKey(data, native.keys[key]),
435
- truncate: native.truncateToWidth,
436
- visibleWidth: native.visibleWidth,
437
- theme
438
- }),
439
- {
440
- overlay: true,
441
- overlayOptions: {
442
- anchor: "center",
443
- width: "96%",
444
- maxHeight: "90%",
445
- minWidth: 96
446
- }
447
- }
448
- );
449
- if (result.kind === "saved") {
450
- const detail = result.changedRoles.length > 0 ? ` Updated: ${result.changedRoles.join(", ")}.` : " No file content changed.";
451
- ctx.ui.notify(
452
- `Saved global Thoth specialist tools.${detail} Saved settings apply on subsequent specialist discovery; running children and the ambient root are unchanged. Native settings or project definitions may override them.`,
453
- "info"
454
- );
455
- }
456
- } catch (error) {
457
- ctx.ui.notify(
458
- `Unable to open global specialist tools: ${error instanceof Error ? error.message : String(error)}`,
459
- "error"
460
- );
461
- }
462
- }
463
- });
55
+ shared[toolsRegistryKey] = {
56
+ ...toolsRegistry(),
57
+ version: 1,
58
+ adapter: createPiToolsPanelAdapter(globalPiRoot(options))
59
+ };
60
+ let toolsOwnershipWarned = false;
464
61
  pi.on("input", (event, ctx) => {
465
- languageAnchorCandidate = (event.source === "interactive" || event.source === "rpc") && typeof event.text === "string" && event.text.trim() && ctx?.isIdle() ? event.text : void 0;
62
+ languageAnchorCandidate = (event.source === "interactive" || event.source === "rpc") && typeof event.text === "string" && event.text.trim() && ctx?.isIdle?.() ? event.text : void 0;
466
63
  });
467
64
  pi.on("before_agent_start", (event) => {
468
65
  const candidate = languageAnchorCandidate;
@@ -490,7 +87,14 @@ function thothAgentsPiExtension(pi, options = {}) {
490
87
  )
491
88
  };
492
89
  });
493
- pi.on("session_start", () => {
90
+ pi.on("session_start", (_event, ctx) => {
91
+ if (!toolsOwnershipWarned && !hasToolsOwnership(pi) && ctx?.ui?.notify) {
92
+ toolsOwnershipWarned = true;
93
+ ctx.ui.notify(
94
+ "/subagents-tools requires @thoth-agents/pi-subagents >=0.3.0. Upgrade thoth-agents and pi-subagents together, then /reload.",
95
+ "warning"
96
+ );
97
+ }
494
98
  try {
495
99
  const packageRoot = options.packageRoot ?? findPackageRoot(dirname(fileURLToPath(import.meta.url)));
496
100
  if (!packageRoot) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thoth-agents",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "Adaptive multi-harness orchestration with six roles, runtime-autonomous AI-first execution routes, and external provider boundaries.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,7 +51,8 @@
51
51
  ]
52
52
  },
53
53
  "scripts": {
54
- "build": "pnpm run integration:sync && tsup && tsc --emitDeclarationOnly && pnpm run generate-schema && pnpm run build:pi-extensions",
54
+ "build": "pnpm run generate:result-contract && pnpm run integration:sync && tsup && tsc --emitDeclarationOnly && pnpm run generate-schema && pnpm run build:pi-extensions",
55
+ "generate:result-contract": "tsx scripts/generate-result-contract.ts",
55
56
  "generate-schema": "tsx scripts/generate-schema.ts",
56
57
  "integration:sync": "tsx src/harness/generate-integration-packages.ts",
57
58
  "integration:verify": "vitest run src/harness/generate-integration-packages.test.ts src/harness/integration-lifecycle.test.ts",
@@ -59,8 +60,9 @@
59
60
  "setup:codex:local": "pnpm run build && tsx scripts/setup-codex-local.ts",
60
61
  "setup:pi:local": "pnpm run build && node scripts/setup-pi-local.mjs",
61
62
  "release:notes": "tsx scripts/generate-release-notes.ts",
62
- "typecheck": "tsc --noEmit",
63
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.evals.json",
63
64
  "test": "vitest run",
65
+ "eval:pi": "vitest run --config vitest.evals.live.config.ts --reporter=verbose --silent=false",
64
66
  "test:pi-extensions": "node --test --test-timeout=110000 scripts/pi-extension-bundles.test.mjs",
65
67
  "lint": "biome lint .",
66
68
  "format": "biome format . --write",
@@ -2,10 +2,10 @@
2
2
  "schemaVersion": 1,
3
3
  "owner": "thoth-agents",
4
4
  "files": {
5
- "agents/thoth-explorer.md": "d9b47792d85e479ed3e0179671223708f097e18c2173320d8a7eb1ab7886775a",
6
- "agents/thoth-librarian.md": "82e5e3c1e3e8b589937382e8fb66cf3425914685d88edf968b2b52657f284511",
7
- "agents/thoth-oracle.md": "808b2b6cfe9d48113704c64737d836cc14ff7fc2e1c5f87486f3e985aa8b92f5",
8
- "agents/thoth-designer.md": "20cc2042c71ad2e2a783d11d75308fee5331a65eb3f066bc0d4046f117031382",
9
- "agents/thoth-worker.md": "de9d9e6230ce16279c5bc929d9c1b65574c2fc589aec9a98fe5ea7cebd447323"
5
+ "agents/thoth-explorer.md": "ab198cc0daaa3d975d1b018098415fff3547462e842c07f279a4951f4013c068",
6
+ "agents/thoth-librarian.md": "c353faf5d84808a21b33ef57986acd9a2e2e37540538048afc97a22002cd48e4",
7
+ "agents/thoth-oracle.md": "0e682b7168aca7fa2f880ce79c39ff2f68cf5bc0ed5cd2b55d0861958fe14518",
8
+ "agents/thoth-designer.md": "d83b3fbcdbca611b208b7fd153a13535819388dce053b8c666d44f2dc22bfcae",
9
+ "agents/thoth-worker.md": "d60e4f9bdb9fe514bd7ef1ce05d7ab16653d56ec08a900497857fe561b48ef14"
10
10
  }
11
11
  }