thoth-agents 0.11.0 → 0.13.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 (95) hide show
  1. package/README.md +16 -5
  2. package/dist/agents/agent-config.d.ts +13 -0
  3. package/dist/agents/index.d.ts +1 -1
  4. package/dist/agents/orchestrator.d.ts +1 -1
  5. package/dist/{chunk-I5TXGNSG.js → chunk-5MYXVRZZ.js} +634 -366
  6. package/dist/chunk-6O4GKDS7.js +207 -0
  7. package/dist/{chunk-RWQPMWEM.js → chunk-JLU3FSBT.js} +17 -2
  8. package/dist/chunk-MIDGZE34.js +107 -0
  9. package/dist/{chunk-HACUJFKI.js → chunk-NOLDFT7O.js} +4 -230
  10. package/dist/{chunk-5WPP6IY7.js → chunk-VVYAQTYE.js} +2 -2
  11. package/dist/{chunk-DNPKVFOB.js → chunk-YCWGTDFE.js} +604 -315
  12. package/dist/cli/config-io.d.ts +2 -2
  13. package/dist/cli/index.js +94 -41
  14. package/dist/cli/install.d.ts +1 -0
  15. package/dist/cli/opencode-profiles.d.ts +10 -0
  16. package/dist/cli/opencode-skill-consent.d.ts +5 -0
  17. package/dist/cli/operations/opencode.d.ts +2 -1
  18. package/dist/cli/operations/types.d.ts +1 -0
  19. package/dist/cli/owned-skills.d.ts +16 -0
  20. package/dist/cli/paths.d.ts +1 -1
  21. package/dist/cli/providers.d.ts +1 -1
  22. package/dist/cli/system.d.ts +3 -1
  23. package/dist/cli/thoth-mem-install.d.ts +10 -10
  24. package/dist/cli/tui/index.js +53 -5
  25. package/dist/cli/tui/operations.d.ts +2 -0
  26. package/dist/cli/types.d.ts +2 -4
  27. package/dist/config/loader.d.ts +2 -5
  28. package/dist/config/schema.d.ts +0 -53
  29. package/dist/harness/adapters/opencode.d.ts +1 -1
  30. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -8
  31. package/dist/hooks/foreground-fallback/index.d.ts +14 -69
  32. package/dist/hooks/index.d.ts +0 -2
  33. package/dist/hooks/json-error-recovery/hook.d.ts +2 -15
  34. package/dist/hooks/oracle-gate-verdict/hook.d.ts +4 -11
  35. package/dist/index.d.ts +7 -4
  36. package/dist/index.js +1603 -1408
  37. package/dist/opencode/agents.d.ts +3 -0
  38. package/dist/opencode/command.d.ts +3 -0
  39. package/dist/opencode/context.d.ts +36 -0
  40. package/dist/opencode/delegation-hooks.d.ts +3 -0
  41. package/dist/opencode/mcp.d.ts +3 -0
  42. package/dist/opencode/permission-rules.d.ts +9 -0
  43. package/dist/opencode/permissions.d.ts +3 -0
  44. package/dist/opencode/profile-rpc-handlers.d.ts +3 -0
  45. package/dist/opencode/profile-rpc.d.ts +688 -0
  46. package/dist/opencode/profile-runtime.d.ts +3 -0
  47. package/dist/opencode/profile-sessions.d.ts +18 -0
  48. package/dist/opencode/profiles/editor.d.ts +44 -0
  49. package/dist/opencode/profiles/index.d.ts +18 -0
  50. package/dist/opencode/profiles/resolver.d.ts +12 -0
  51. package/dist/opencode/profiles/store.d.ts +63 -0
  52. package/dist/opencode/profiles/types.d.ts +26 -0
  53. package/dist/opencode/session-hooks.d.ts +3 -0
  54. package/dist/opencode/session-state.d.ts +39 -0
  55. package/dist/opencode/skills.d.ts +3 -0
  56. package/dist/opencode/tool-overrides/evaluator.d.ts +13 -0
  57. package/dist/opencode/tool-overrides/index.d.ts +5 -0
  58. package/dist/opencode/tool-overrides/store.d.ts +38 -0
  59. package/dist/opencode/tool-overrides/types.d.ts +32 -0
  60. package/dist/opencode/tool-overrides/validation.d.ts +5 -0
  61. package/dist/opencode/tool-overrides/wildcard.d.ts +3 -0
  62. package/dist/opencode/tool-rpc-handlers.d.ts +3 -0
  63. package/dist/opencode/tool-rpc.d.ts +428 -0
  64. package/dist/opencode/tools.d.ts +3 -0
  65. package/dist/pi.js +3 -3
  66. package/dist/tools/ast-grep/cli.d.ts +1 -0
  67. package/dist/tools/ast-grep/index.d.ts +108 -2
  68. package/dist/tools/ast-grep/tools.d.ts +107 -3
  69. package/dist/tools/define-tool.d.ts +23 -0
  70. package/dist/tools/lsp/tools.d.ts +97 -5
  71. package/dist/tui/fixtures.d.ts +2 -0
  72. package/dist/tui/flow.d.ts +16 -0
  73. package/dist/tui/index.d.ts +7 -0
  74. package/dist/tui/index.js +1384 -0
  75. package/dist/tui/model.d.ts +84 -0
  76. package/dist/tui/tools/fixtures.d.ts +3 -0
  77. package/dist/tui/tools/flow.d.ts +11 -0
  78. package/dist/tui/tools/model.d.ts +97 -0
  79. package/dist/utils/index.d.ts +0 -1
  80. package/package.json +18 -3
  81. package/plugin/.claude-plugin/plugin.json +1 -1
  82. package/plugin/.codex-plugin/plugin.json +1 -1
  83. package/plugin/skills/thoth-sdd/SKILL.md +8 -2
  84. package/plugin/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  85. package/skills/thoth-sdd/SKILL.md +8 -2
  86. package/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  87. package/thoth-agents.schema.json +0 -96
  88. package/dist/chunk-62L3ORXN.js +0 -30
  89. package/dist/delegation/project-id.d.ts +0 -10
  90. package/dist/hooks/auto-update-checker/checker.d.ts +0 -23
  91. package/dist/hooks/auto-update-checker/constants.d.ts +0 -11
  92. package/dist/hooks/auto-update-checker/index.d.ts +0 -17
  93. package/dist/hooks/auto-update-checker/types.d.ts +0 -22
  94. package/dist/utils/tmux-session-manager.d.ts +0 -63
  95. package/dist/utils/tmux.d.ts +0 -32
@@ -0,0 +1,84 @@
1
+ /** Pure view-model for the `/subagents-model` panel: no dialogs, no RPC. */
2
+ import type { ProfileRpcInput, ProfileState } from '../opencode/profile-rpc';
3
+ export declare const DEFAULT_PROFILE = "default";
4
+ export type Profile = ProfileRpcInput<'create'>['profile'];
5
+ export type RoleModel = Profile['agents'][keyof Profile['agents'] & string];
6
+ export type Role = ProfileState['roles'][number];
7
+ export type Scope = 'global' | 'project';
8
+ export type SelectionScope = 'session' | 'project' | 'global';
9
+ export type ProfileKey = 'default' | `${Scope}:${string}`;
10
+ export interface ProfileRow {
11
+ key: ProfileKey;
12
+ name: string;
13
+ /** Undefined for the virtual `default`. */
14
+ scope?: Scope;
15
+ shadowed: boolean;
16
+ profile: Profile;
17
+ revision?: string;
18
+ }
19
+ export interface Markers {
20
+ effective: boolean;
21
+ session: boolean;
22
+ project: boolean;
23
+ global: boolean;
24
+ }
25
+ export declare const profileKey: (name: string, scope?: Scope) => ProfileKey;
26
+ export declare function profileRows(state: ProfileState): ProfileRow[];
27
+ export declare function markersFor(state: ProfileState, row: ProfileRow): Markers;
28
+ export declare const markerLetters: (markers: Markers) => string;
29
+ /** `● SPG name` — ● marks the profile runs currently use. */
30
+ export declare function profileTitle(row: ProfileRow, markers: Markers): string;
31
+ export declare function profileDescription(row: ProfileRow): string;
32
+ export declare const PROFILE_LEGEND = "\u25CF effective \u00B7 S session \u00B7 P project pin \u00B7 G global \u00B7 ~ shadowed";
33
+ export interface RoleView {
34
+ role: Role;
35
+ /** True when the row has no own model and shows a fallback. */
36
+ inherited: boolean;
37
+ model?: string;
38
+ variant?: string;
39
+ /** Where an inherited value comes from. */
40
+ inheritedFrom?: 'default' | 'session';
41
+ /** False when an own variant-only entry overrides the inherited variant. */
42
+ variantInherited?: boolean;
43
+ }
44
+ /** model + variant are a pair: an entry with a model owns its variant. */
45
+ export declare function roleViews(state: ProfileState, profile: Profile, isDefault: boolean): RoleView[];
46
+ export declare function roleLabel(view: RoleView): string;
47
+ export declare function roleDescription(view: RoleView): string;
48
+ export interface ModelChoice {
49
+ id: string;
50
+ name: string;
51
+ variants: string[];
52
+ }
53
+ export declare function providerModels(state: ProfileState, providerID: string): {
54
+ id: string;
55
+ name: string;
56
+ models: ModelChoice[];
57
+ } | undefined;
58
+ export declare function variantsOf(state: ProfileState, modelId: string): string[];
59
+ /** Case-insensitive search over title, id and description. */
60
+ export declare function searchOptions<T extends {
61
+ title: string;
62
+ description?: string;
63
+ value: unknown;
64
+ }>(query: string, options: readonly T[]): T[];
65
+ export declare function validateProfileName(raw: string, scope: Scope, state: ProfileState): string | undefined;
66
+ /** Draft-until-save working copy of one persisted or new profile. */
67
+ export interface Draft {
68
+ scope: Scope;
69
+ name: string;
70
+ isNew: boolean;
71
+ /** Last persisted profile, for dirty detection. */
72
+ base: Profile;
73
+ /** Opaque token from the server; undefined until a new draft is created. */
74
+ revision?: string;
75
+ profile: Profile;
76
+ }
77
+ export declare const isDirty: (draft: Draft) => boolean;
78
+ export declare function withRole(profile: Profile, role: Role, value: RoleModel | undefined): Profile;
79
+ export declare function cloneProfile(profile: Profile): Profile;
80
+ export declare function openDraft(row: ProfileRow): Draft | undefined;
81
+ export declare function newDraft(scope: Scope, name: string, from: Profile): Draft;
82
+ export declare function activationMessage(scope: SelectionScope, name: string, state: ProfileState): string;
83
+ export declare function clearMessage(scope: SelectionScope, state: ProfileState): string;
84
+ export declare function selectionClearable(state: ProfileState, sessionAvailable: boolean): SelectionScope[];
@@ -0,0 +1,3 @@
1
+ import type { ToolState } from '../../opencode/tool-rpc';
2
+ /** explorer: global deny webfetch + ask github*; designer: project allow bash. */
3
+ export declare function makeToolState(): ToolState;
@@ -0,0 +1,11 @@
1
+ /** `/subagents-tools` flow. Talks only to injected UI and RPC ports. */
2
+ import type { ToolRpcInput, ToolRpcMethod, ToolRpcOutput } from '../../opencode/tool-rpc';
3
+ import type { Ui } from '../flow';
4
+ export type ToolsRpcPort = {
5
+ [M in ToolRpcMethod]: (input: ToolRpcInput<M>) => Promise<ToolRpcOutput<M>>;
6
+ };
7
+ export interface ToolsFlowPorts {
8
+ ui: Ui;
9
+ rpc: ToolsRpcPort;
10
+ }
11
+ export declare function runToolsFlow(ports: ToolsFlowPorts): Promise<void>;
@@ -0,0 +1,97 @@
1
+ /** Pure view-model for the `/subagents-tools` panel: no dialogs, no RPC. */
2
+ import type { ToolState } from '../../opencode/tool-rpc';
3
+ export type ToolRole = ToolState['specialists'][number];
4
+ export type RoleState = ToolState['roles'][number];
5
+ export type Scope = 'global' | 'project';
6
+ export type Effect = 'allow' | 'ask' | 'deny';
7
+ export interface Override {
8
+ action: string;
9
+ effect: Effect;
10
+ }
11
+ export declare const LOCKED_ACTION = "subagent";
12
+ export declare const LOCKED_MESSAGE = "subagent is locked: specialists can never delegate, so it cannot be changed.";
13
+ /** Mirrors OpenCode wildcard semantics (`src/opencode/tool-overrides/wildcard.ts`) without pulling in fs code. */
14
+ export declare function matchAction(input: string, pattern: string, platform?: string): boolean;
15
+ /** Inherit removes every exact entry, leaving broader patterns intact. */
16
+ export declare const removeOverride: (overrides: readonly Override[], action: string) => Override[];
17
+ /** Explicit choices go last so broader patterns cannot supersede the edit. */
18
+ export declare const setOverride: (overrides: readonly Override[], value: Override) => Override[];
19
+ export declare const hasWildcard: (pattern: string) => boolean;
20
+ export declare const roleOf: (state: ToolState, role: ToolRole) => RoleState;
21
+ export interface ToolDraft {
22
+ role: ToolRole;
23
+ scope: Scope;
24
+ /** Overrides as last saved/loaded, for dirty detection. */
25
+ base: Override[];
26
+ overrides: Override[];
27
+ /** Whether the scope has an entry for the role (absent differs from an explicit empty list). */
28
+ present: boolean;
29
+ basePresent: boolean;
30
+ revision: string;
31
+ }
32
+ /** Overrides of the given scope; used when opening a draft. */
33
+ export declare function openDraft(state: ToolState, role: ToolRole, scope: Scope): ToolDraft;
34
+ export declare const isDirty: (draft: ToolDraft) => boolean;
35
+ /** Replaces the draft overrides; a changed project list makes the project entry present. */
36
+ export declare function applyOverrides(draft: ToolDraft, next: Override[]): void;
37
+ /** Makes an absent project entry an explicit empty list that masks global overrides. */
38
+ export declare function maskGlobal(draft: ToolDraft): void;
39
+ export interface EffectiveLayer {
40
+ /** Scope whose overrides apply after saving the draft. */
41
+ scope: 'default' | Scope;
42
+ overrides: Override[];
43
+ /** False when the edited scope would be shadowed by a project entry. */
44
+ controls: boolean;
45
+ }
46
+ /** Layering: a present project entry (even empty) replaces global entirely. */
47
+ export declare function effectiveLayer(state: ToolState, draft: ToolDraft): EffectiveLayer;
48
+ /** True when the edited scope is shadowed by a present project entry. */
49
+ export declare const isInactiveScope: (state: ToolState, draft: ToolDraft) => boolean;
50
+ /** Status line for the role view title. */
51
+ export declare function scopeStatus(state: ToolState, draft: ToolDraft): string;
52
+ /** Scope to preselect: the role's effective scope, else global. */
53
+ export declare const defaultScope: (role: RoleState) => Scope;
54
+ /** `2 denied, 1 ask` over the overrides in force for a role. */
55
+ export declare function roleSummary(role: RoleState): string;
56
+ export declare const scopeMarker: (role: RoleState) => string;
57
+ export declare function overviewTitle(role: RoleState): string;
58
+ export declare const OVERVIEW_LEGEND = "G global \u00B7 P project \u00B7 project replaces global";
59
+ export interface ActionRow {
60
+ action: string;
61
+ tools: string[];
62
+ locked: boolean;
63
+ effect: Effect;
64
+ /** `default` for untouched values, `entry` for exact entries, `pattern` for wildcard matches. */
65
+ source: 'default' | 'entry' | 'pattern';
66
+ via?: string;
67
+ /** Scope that supplies the shown effect after save. */
68
+ origin: 'default' | Scope;
69
+ /** Scope being edited. */
70
+ editScope: Scope;
71
+ /** True when a project entry shadows the edited global scope. */
72
+ inactive: boolean;
73
+ /** Effect the edited scope would give on its own while it is inactive. */
74
+ scopeEffect?: Effect;
75
+ /** True when the value differs from what state reports as saved. */
76
+ changed: boolean;
77
+ }
78
+ /** Preview the effective layer after save, over shipped effects, last-match-wins. */
79
+ export declare function previewRows(state: ToolState, draft: ToolDraft): ActionRow[];
80
+ export declare function rowTitle(row: ActionRow): string;
81
+ export declare function rowDescription(row: ActionRow): string;
82
+ export declare const ROLE_LEGEND = "\u25CF override \u00B7 \u25CB via pattern \u00B7 \u00B7 default \u00B7 \uD83D\uDD12 locked";
83
+ export interface ManualEntry extends Override {
84
+ matchCount: number;
85
+ }
86
+ /** Number of inventory actions an entry matches. */
87
+ export declare const countMatches: (state: ToolState, pattern: string) => number;
88
+ /** Entries that are wildcards or name no registered action (e.g. disconnected MCP servers). */
89
+ export declare function manualEntries(state: ToolState, draft: ToolDraft): ManualEntry[];
90
+ export declare const entryDescription: (entry: ManualEntry) => string;
91
+ export declare function validatePattern(raw: string): string | undefined;
92
+ /** Case-insensitive search over title and description. */
93
+ export declare function searchOptions<T extends {
94
+ title: string;
95
+ description?: string;
96
+ value: unknown;
97
+ }>(query: string, options: readonly T[]): T[];
@@ -3,5 +3,4 @@ export * from './env';
3
3
  export * from './internal-initiator';
4
4
  export { log } from './logger';
5
5
  export * from './polling';
6
- export * from './tmux';
7
6
  export { extractZip } from './zip-extractor';
package/package.json CHANGED
@@ -1,9 +1,25 @@
1
1
  {
2
2
  "name": "thoth-agents",
3
- "version": "0.11.0",
3
+ "version": "0.13.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",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./tui": {
13
+ "types": "./dist/tui/index.d.ts",
14
+ "default": "./dist/tui/index.js"
15
+ },
16
+ "./package.json": "./package.json",
17
+ "./dist/*": "./dist/*",
18
+ "./skills/*": "./skills/*",
19
+ "./plugin/*": "./plugin/*",
20
+ "./pi/*": "./pi/*",
21
+ "./thoth-agents.schema.json": "./thoth-agents.schema.json"
22
+ },
7
23
  "bin": {
8
24
  "thoth-agents": "./dist/cli/index.js"
9
25
  },
@@ -81,8 +97,6 @@
81
97
  "dependencies": {
82
98
  "@ast-grep/cli": "^0.40.0",
83
99
  "@modelcontextprotocol/sdk": "^1.26.0",
84
- "@opencode-ai/plugin": "1.18.2",
85
- "@opencode-ai/sdk": "1.18.2",
86
100
  "hosted-git-info": "9.0.3",
87
101
  "ink": "^7.0.3",
88
102
  "react": "^19.2.6",
@@ -103,6 +117,7 @@
103
117
  "@earendil-works/pi-ai": "1.5.0",
104
118
  "@earendil-works/pi-coding-agent": "1.5.0",
105
119
  "@earendil-works/pi-tui": "1.5.0",
120
+ "@opencode/plugin": "2.2.5",
106
121
  "@types/hosted-git-info": "3.0.5",
107
122
  "@types/node": "^22.19.19",
108
123
  "@types/react": "^19.2.15",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thoth-agents",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Adaptive multi-harness agent pack with six roles and proportional SDD governance for Claude Code.",
5
5
  "author": {
6
6
  "name": "thoth-agents"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thoth-agents",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Bundled AI-first work skills and MCP configuration; the six-role global Codex layer requires thoth-agents CLI setup.",
5
5
  "skills": "./skills/",
6
6
  "mcpServers": "./codex.mcp.json",
@@ -206,7 +206,13 @@ Validation is not authorization.
206
206
 
207
207
  Delivery differs by harness: Pi attaches verdicts to completion notifications,
208
208
  `subagent_result`, and task-mode `subagent_run` (match `taskId`); OpenCode appends
209
- them to Oracle `task` output. Claude Code verdicts arrive with Subagent hand-back
209
+ them to completed foreground Oracle `subagent` tool output. OpenCode background
210
+ completions arrive automatically as `<subagent sessionID=... state="completed">`
211
+ messages without a `thoth-gate-verdict` line; root must use the explicit compact
212
+ validator fallback below. Delegate with `subagent` arguments `agent`,
213
+ `description`, `prompt`, optional `model`, `background:true` for independent work,
214
+ and `sessionID` to continue or clarify a child, including while running. No
215
+ status polling tool exists; default nesting depth is 1. Claude Code verdicts arrive with Subagent hand-back
210
216
  reports (foreground or background; `agent` is the agent id, match it to the Agent
211
217
  result's `agentId`) and completed foreground `Agent` results whose content carries
212
218
  the report. Other deliveries (e.g. background runs without hand-back) carry none;
@@ -220,7 +226,7 @@ result.
220
226
  ### Explicit compact fallback
221
227
 
222
228
  Only when no verdict was delivered (hook unavailable/untrusted, a Codex multi-agent
223
- v2 delivery, or a Claude background run without hand-back), resolve `<skill-dir>` from the installed
229
+ v2 delivery, an OpenCode background completion, or a Claude background run without hand-back), resolve `<skill-dir>` from the installed
224
230
  thoth-sdd SKILL.md directory, exactly as for `scripts/validate.mjs`, and run:
225
231
 
226
232
  ```text
@@ -122,12 +122,38 @@ function checkExpectations(options, issue) {
122
122
  );
123
123
  }
124
124
 
125
+ const isFenceLine = (line) => /^(?:`{3,}|~{3,})/.test(line);
126
+ const isVerdictLine = (line) => line.startsWith('thoth-gate-verdict ');
127
+
128
+ function unwrapSubagent(message, lines) {
129
+ if (!message.startsWith('<subagent ')) return message;
130
+ if (lines.at(-1) !== '</subagent>') return '';
131
+ const opener = lines.lastIndexOf('```thoth-result');
132
+ if (opener < 1) return '';
133
+ const header = lines.slice(0, opener).join('\n').trimEnd();
134
+ // Native descriptions are interpolated verbatim, not escaped attributes.
135
+ // Only structural lines and the completed-state marker are authoritative.
136
+ if (
137
+ !header.includes('state="completed"') ||
138
+ lines
139
+ .slice(0, opener)
140
+ .some((line) => isFenceLine(line) || isVerdictLine(line))
141
+ )
142
+ return '';
143
+ return lines.slice(opener, -1).join('\n').trim();
144
+ }
145
+
125
146
  function parseEnvelope(message) {
126
- const trimmed = typeof message === 'string' ? message.trim() : '';
147
+ const complete = typeof message === 'string' ? message.trim() : '';
148
+ const lines = complete.split(/\r\n|\r|\n/);
149
+ // Check structural lines in the complete delivery, including the header.
150
+ const singleEnvelope =
151
+ lines.filter(isFenceLine).length === 2 && !lines.some(isVerdictLine);
152
+ const trimmed = singleEnvelope ? unwrapSubagent(complete, lines) : '';
127
153
  const block = new RegExp(
128
154
  `^\`\`\`${contract.fence}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\`$`,
129
155
  ).exec(trimmed);
130
- if (!block || (trimmed.match(/^```.*$/gm) ?? []).length !== 2) {
156
+ if (!block) {
131
157
  return {
132
158
  issues: [
133
159
  {
@@ -206,7 +206,13 @@ Validation is not authorization.
206
206
 
207
207
  Delivery differs by harness: Pi attaches verdicts to completion notifications,
208
208
  `subagent_result`, and task-mode `subagent_run` (match `taskId`); OpenCode appends
209
- them to Oracle `task` output. Claude Code verdicts arrive with Subagent hand-back
209
+ them to completed foreground Oracle `subagent` tool output. OpenCode background
210
+ completions arrive automatically as `<subagent sessionID=... state="completed">`
211
+ messages without a `thoth-gate-verdict` line; root must use the explicit compact
212
+ validator fallback below. Delegate with `subagent` arguments `agent`,
213
+ `description`, `prompt`, optional `model`, `background:true` for independent work,
214
+ and `sessionID` to continue or clarify a child, including while running. No
215
+ status polling tool exists; default nesting depth is 1. Claude Code verdicts arrive with Subagent hand-back
210
216
  reports (foreground or background; `agent` is the agent id, match it to the Agent
211
217
  result's `agentId`) and completed foreground `Agent` results whose content carries
212
218
  the report. Other deliveries (e.g. background runs without hand-back) carry none;
@@ -220,7 +226,7 @@ result.
220
226
  ### Explicit compact fallback
221
227
 
222
228
  Only when no verdict was delivered (hook unavailable/untrusted, a Codex multi-agent
223
- v2 delivery, or a Claude background run without hand-back), resolve `<skill-dir>` from the installed
229
+ v2 delivery, an OpenCode background completion, or a Claude background run without hand-back), resolve `<skill-dir>` from the installed
224
230
  thoth-sdd SKILL.md directory, exactly as for `scripts/validate.mjs`, and run:
225
231
 
226
232
  ```text
@@ -122,12 +122,38 @@ function checkExpectations(options, issue) {
122
122
  );
123
123
  }
124
124
 
125
+ const isFenceLine = (line) => /^(?:`{3,}|~{3,})/.test(line);
126
+ const isVerdictLine = (line) => line.startsWith('thoth-gate-verdict ');
127
+
128
+ function unwrapSubagent(message, lines) {
129
+ if (!message.startsWith('<subagent ')) return message;
130
+ if (lines.at(-1) !== '</subagent>') return '';
131
+ const opener = lines.lastIndexOf('```thoth-result');
132
+ if (opener < 1) return '';
133
+ const header = lines.slice(0, opener).join('\n').trimEnd();
134
+ // Native descriptions are interpolated verbatim, not escaped attributes.
135
+ // Only structural lines and the completed-state marker are authoritative.
136
+ if (
137
+ !header.includes('state="completed"') ||
138
+ lines
139
+ .slice(0, opener)
140
+ .some((line) => isFenceLine(line) || isVerdictLine(line))
141
+ )
142
+ return '';
143
+ return lines.slice(opener, -1).join('\n').trim();
144
+ }
145
+
125
146
  function parseEnvelope(message) {
126
- const trimmed = typeof message === 'string' ? message.trim() : '';
147
+ const complete = typeof message === 'string' ? message.trim() : '';
148
+ const lines = complete.split(/\r\n|\r|\n/);
149
+ // Check structural lines in the complete delivery, including the header.
150
+ const singleEnvelope =
151
+ lines.filter(isFenceLine).length === 2 && !lines.some(isVerdictLine);
152
+ const trimmed = singleEnvelope ? unwrapSubagent(complete, lines) : '';
127
153
  const block = new RegExp(
128
154
  `^\`\`\`${contract.fence}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\`$`,
129
155
  ).exec(trimmed);
130
- if (!block || (trimmed.match(/^```.*$/gm) ?? []).length !== 2) {
156
+ if (!block) {
131
157
  return {
132
158
  issues: [
133
159
  {
@@ -5,9 +5,6 @@
5
5
  "$schema": {
6
6
  "type": "string"
7
7
  },
8
- "preset": {
9
- "type": "string"
10
- },
11
8
  "setDefaultAgent": {
12
9
  "type": "boolean"
13
10
  },
@@ -164,73 +161,6 @@
164
161
  ],
165
162
  "additionalProperties": false
166
163
  },
167
- "presets": {
168
- "type": "object",
169
- "propertyNames": {
170
- "type": "string"
171
- },
172
- "additionalProperties": {
173
- "type": "object",
174
- "propertyNames": {
175
- "type": "string",
176
- "pattern": "^(?!quick$|deep$).+$"
177
- },
178
- "additionalProperties": {
179
- "type": "object",
180
- "properties": {
181
- "model": {
182
- "anyOf": [
183
- {
184
- "type": "string"
185
- },
186
- {
187
- "type": "array",
188
- "items": {
189
- "anyOf": [
190
- {
191
- "type": "string"
192
- },
193
- {
194
- "type": "object",
195
- "properties": {
196
- "id": {
197
- "type": "string"
198
- },
199
- "variant": {
200
- "type": "string"
201
- }
202
- },
203
- "required": ["id"]
204
- }
205
- ]
206
- }
207
- }
208
- ]
209
- },
210
- "skills": {
211
- "type": "array",
212
- "items": {
213
- "type": "string",
214
- "minLength": 1
215
- }
216
- },
217
- "temperature": {
218
- "type": "number",
219
- "minimum": 0,
220
- "maximum": 2
221
- },
222
- "steps": {
223
- "type": "integer",
224
- "minimum": 1,
225
- "maximum": 9007199254740991
226
- },
227
- "variant": {
228
- "type": "string"
229
- }
230
- }
231
- }
232
- }
233
- },
234
164
  "agents": {
235
165
  "type": "object",
236
166
  "propertyNames": {
@@ -298,32 +228,6 @@
298
228
  "type": "string"
299
229
  }
300
230
  },
301
- "tmux": {
302
- "type": "object",
303
- "properties": {
304
- "enabled": {
305
- "default": false,
306
- "type": "boolean"
307
- },
308
- "layout": {
309
- "default": "main-vertical",
310
- "type": "string",
311
- "enum": [
312
- "main-horizontal",
313
- "main-vertical",
314
- "tiled",
315
- "even-horizontal",
316
- "even-vertical"
317
- ]
318
- },
319
- "main_pane_size": {
320
- "default": 60,
321
- "type": "number",
322
- "minimum": 20,
323
- "maximum": 80
324
- }
325
- }
326
- },
327
231
  "fallback": {
328
232
  "type": "object",
329
233
  "properties": {
@@ -1,30 +0,0 @@
1
- // src/mcp/context7.ts
2
- var CONTEXT7_MCP_URL = "https://mcp.context7.com/mcp";
3
- var context7 = {
4
- type: "remote",
5
- url: CONTEXT7_MCP_URL,
6
- headers: process.env.CONTEXT7_API_KEY ? { CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY } : void 0,
7
- oauth: false
8
- };
9
-
10
- // src/mcp/exa.ts
11
- var exa = {
12
- type: "local",
13
- command: ["npx", "-y", "exa-mcp-server"]
14
- };
15
-
16
- // src/mcp/grep-app.ts
17
- var GREP_APP_MCP_URL = "https://mcp.grep.app";
18
- var grep_app = {
19
- type: "remote",
20
- url: GREP_APP_MCP_URL,
21
- oauth: false
22
- };
23
-
24
- export {
25
- CONTEXT7_MCP_URL,
26
- context7,
27
- exa,
28
- GREP_APP_MCP_URL,
29
- grep_app
30
- };
@@ -1,10 +0,0 @@
1
- import type { PluginInput } from '@opencode-ai/plugin';
2
- interface ProjectIdOptions {
3
- worktreeDirectory: string;
4
- projectName?: string;
5
- shell?: PluginInput['$'];
6
- timeoutMs?: number;
7
- }
8
- export declare function getProjectId(directory: string, timeoutMs?: number, projectName?: string, shell?: PluginInput['$']): Promise<string | null>;
9
- export declare function getProjectId(options: ProjectIdOptions): Promise<string | null>;
10
- export {};
@@ -1,23 +0,0 @@
1
- import type { PluginEntryInfo } from './types';
2
- /**
3
- * Extracts the update channel (latest, alpha, beta, etc.) from a version string.
4
- * @param version The version or tag to analyze.
5
- * @returns The channel name.
6
- */
7
- export declare function extractChannel(version: string | null): string;
8
- /**
9
- * Resolves the version of the plugin when running in local development mode.
10
- */
11
- export declare function getLocalDevVersion(directory: string): string | null;
12
- /**
13
- * Searches across all config locations to find the current installation entry for this plugin.
14
- */
15
- export declare function findPluginEntry(directory: string): PluginEntryInfo | null;
16
- /**
17
- * Resolves the installed version from node_modules, with memoization.
18
- */
19
- export declare function getCachedVersion(): string | null;
20
- /**
21
- * Fetches the latest version for a specific channel from the NPM registry.
22
- */
23
- export declare function getLatestVersion(channel?: string): Promise<string | null>;
@@ -1,11 +0,0 @@
1
- export declare const PACKAGE_NAME = "thoth-agents";
2
- export declare const NPM_REGISTRY_URL = "https://registry.npmjs.org/-/package/thoth-agents/dist-tags";
3
- export declare const NPM_FETCH_TIMEOUT = 5000;
4
- /** The directory used by OpenCode to cache node_modules for plugins. */
5
- export declare const CACHE_DIR: string;
6
- /** Path to this plugin's package.json within the OpenCode cache. */
7
- export declare const INSTALLED_PACKAGE_JSON: string;
8
- /** Primary OpenCode configuration file path (standard JSON). */
9
- export declare const USER_OPENCODE_CONFIG: string;
10
- /** Alternative OpenCode configuration file path (JSON with Comments). */
11
- export declare const USER_OPENCODE_CONFIG_JSONC: string;
@@ -1,17 +0,0 @@
1
- import type { PluginInput } from '@opencode-ai/plugin';
2
- import type { AutoUpdateCheckerOptions } from './types';
3
- /**
4
- * Creates an OpenCode hook that checks for plugin updates when a new session is created.
5
- * @param ctx The plugin input context.
6
- * @param options Configuration options for the update checker.
7
- * @returns A hook object for the session.created event.
8
- */
9
- export declare function createAutoUpdateCheckerHook(ctx: PluginInput, options?: AutoUpdateCheckerOptions): {
10
- event: ({ event }: {
11
- event: {
12
- type: string;
13
- properties?: unknown;
14
- };
15
- }) => void;
16
- };
17
- export type { AutoUpdateCheckerOptions } from './types';
@@ -1,22 +0,0 @@
1
- export interface NpmDistTags {
2
- latest: string;
3
- [key: string]: string;
4
- }
5
- export interface OpencodeConfig {
6
- plugin?: string[];
7
- [key: string]: unknown;
8
- }
9
- export interface PackageJson {
10
- version: string;
11
- name?: string;
12
- [key: string]: unknown;
13
- }
14
- export interface AutoUpdateCheckerOptions {
15
- showStartupToast?: boolean;
16
- }
17
- export interface PluginEntryInfo {
18
- entry: string;
19
- isPinned: boolean;
20
- pinnedVersion: string | null;
21
- configPath: string;
22
- }