@causyne/agent 1.10.12 → 1.10.17

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 (68) hide show
  1. package/LICENSE +1 -1
  2. package/docs/ARCHITECTURE.md +136 -7
  3. package/docs/SECURITY_REVIEW.md +13 -1
  4. package/docs/THIRD_PARTY_NOTICES.md +43 -10
  5. package/package.json +11 -4
  6. package/src/agentLoop.js +161 -93
  7. package/src/agentProfiles.js +135 -0
  8. package/src/agentProfiles.ts +143 -0
  9. package/src/approvalGrants.js +17 -35
  10. package/src/approvalGrants.ts +54 -0
  11. package/src/auditLog.d.ts +3 -0
  12. package/src/benchmarks/planCache.js +159 -0
  13. package/src/browser/camofoxRuntime.js +93 -0
  14. package/src/browser/camofoxRuntime.ts +112 -0
  15. package/src/browser/causyneBrowser.js +495 -0
  16. package/src/browser/causyneBrowser.ts +594 -0
  17. package/src/browser/searchProviders.js +156 -0
  18. package/src/browser/searchProviders.ts +156 -0
  19. package/src/browser/snapshotWindow.js +28 -0
  20. package/src/browser/snapshotWindow.ts +40 -0
  21. package/src/browser/urlPolicy.js +89 -0
  22. package/src/browser/urlPolicy.ts +89 -0
  23. package/src/checkpoints.js +19 -6
  24. package/src/commands.js +120 -8
  25. package/src/config.d.ts +2 -0
  26. package/src/cortex/autoCondenser.js +1 -1
  27. package/src/hooks.js +150 -0
  28. package/src/hooks.ts +193 -0
  29. package/src/mcp/mcpClient.js +520 -132
  30. package/src/mcp/mcpClient.ts +584 -0
  31. package/src/mcp/mcpRegistry.js +399 -185
  32. package/src/mcp/mcpRegistry.ts +451 -0
  33. package/src/planCache.js +529 -225
  34. package/src/planCache.ts +840 -0
  35. package/src/plugins/pluginManager.js +460 -0
  36. package/src/plugins/pluginRunner.js +50 -0
  37. package/src/plugins/pluginSkills.js +84 -0
  38. package/src/plugins/pluginSkills.ts +106 -0
  39. package/src/provisioning/capabilityProvisioner.d.ts +10 -0
  40. package/src/provisioning/capabilityProvisioner.js +5 -0
  41. package/src/provisioning/capabilityTools.js +2 -1
  42. package/src/sandbox.js +53 -2
  43. package/src/sessionJournal.js +471 -0
  44. package/src/sessionJournal.ts +496 -0
  45. package/src/sessionStore.js +3 -279
  46. package/src/skillsManager.js +18 -0
  47. package/src/subagentJournal.js +215 -0
  48. package/src/subagentJournal.ts +254 -0
  49. package/src/taskShape.js +179 -198
  50. package/src/taskShape.ts +259 -0
  51. package/src/toolPacks.js +6 -3
  52. package/src/tools/browserAutomator.js +45 -241
  53. package/src/tools/browserAutomator.ts +57 -0
  54. package/src/tools/deferredMcpSearch.js +2 -2
  55. package/src/tools/manageLocalAiDependencies.js +1 -1
  56. package/src/tools/pluginManager.js +61 -0
  57. package/src/tools/registry.js +47 -53
  58. package/src/tools/subagentOrchestrator.js +73 -12
  59. package/src/tools/toolForge.js +47 -95
  60. package/src/tools/webFetch.js +183 -68
  61. package/src/tools/webFetch.ts +185 -0
  62. package/src/tools/webSearch.js +50 -75
  63. package/src/tools/webSearch.ts +56 -0
  64. package/src/types/contracts.js +8 -0
  65. package/src/types/contracts.ts +263 -0
  66. package/src/utils/paths.d.ts +3 -0
  67. package/src/version.d.ts +1 -0
  68. package/src/mcp/jsonRpc.js +0 -165
package/src/taskShape.js CHANGED
@@ -1,212 +1,198 @@
1
-
2
-
3
1
  const PACK_SIGNALS = {
4
- game: {
5
- strong: ['game', 'gamedev', 'permainan', 'godot', 'unity', 'unreal', 'rpg', 'platformer', 'roguelike', 'arcade', 'voxel', 'minecraft', 'tilemap', 'spritesheet', 'boss fight', 'level design'],
6
- weak: ['sprite', 'player', 'enemy', 'quest', 'npc', 'gameplay', 'multiplayer', 'leaderboard']
7
- },
8
- web: {
9
- strong: ['web app', 'webapp', 'website', 'situs', 'landing page', 'homepage', 'frontend', 'front-end', 'dashboard', 'portfolio', 'react', 'vue', 'svelte', 'angular', 'nextjs', 'next.js', 'nuxt', 'astro', 'tailwind', 'html', 'css'],
10
- weak: ['web', 'ui', 'ux', 'page', 'halaman', 'responsive', 'browser', 'dom', 'component', 'layout', 'design', 'desain', 'tampilan']
11
- },
12
- media: {
13
- strong: ['generate image', 'generate video', 'generate music', 'text to speech', 'voiceover', 'blender', 'davinci', 'resolve', 'render', '3d model', 'animation', 'animasi'],
14
- weak: ['image', 'gambar', 'photo', 'foto', 'video', 'audio', 'sound', 'suara', 'music', 'musik', 'lagu', 'thumbnail', 'poster', 'logo', 'illustration']
15
- },
16
- system: {
17
- strong: ['kill process', 'port', 'zip', 'unzip', 'tar', 'archive', 'clipboard', 'printer', 'print', 'cetak'],
18
- weak: ['process', 'proses', 'compress', 'extract', 'ekstrak', 'open app', 'launch']
19
- },
20
- advanced: {
21
- strong: ['pdf', 'forge a tool', 'custom tool', 'subagent', 'sub-agent', 'parallel agent'],
22
- weak: ['symbols', 'ast', 'refactor', 'orchestrate', 'swarm']
23
- }
2
+ game: {
3
+ strong: ['game', 'gamedev', 'permainan', 'godot', 'unity', 'unreal', 'rpg', 'platformer', 'roguelike', 'arcade', 'voxel', 'minecraft', 'tilemap', 'spritesheet', 'boss fight', 'level design'],
4
+ weak: ['sprite', 'player', 'enemy', 'quest', 'npc', 'gameplay', 'multiplayer', 'leaderboard']
5
+ },
6
+ web: {
7
+ strong: ['web app', 'webapp', 'website', 'situs', 'landing page', 'homepage', 'frontend', 'front-end', 'dashboard', 'portfolio', 'react', 'vue', 'svelte', 'angular', 'nextjs', 'next.js', 'nuxt', 'astro', 'tailwind', 'html', 'css'],
8
+ weak: ['web', 'ui', 'ux', 'page', 'halaman', 'responsive', 'browser', 'dom', 'component', 'layout', 'design', 'desain', 'tampilan']
9
+ },
10
+ media: {
11
+ strong: ['generate image', 'generate video', 'generate music', 'text to speech', 'voiceover', 'blender', 'davinci', 'resolve', 'render', '3d model', 'animation', 'animasi'],
12
+ weak: ['image', 'gambar', 'photo', 'foto', 'video', 'audio', 'sound', 'suara', 'music', 'musik', 'lagu', 'thumbnail', 'poster', 'logo', 'illustration']
13
+ },
14
+ system: {
15
+ strong: ['kill process', 'port', 'zip', 'unzip', 'tar', 'archive', 'clipboard', 'printer', 'print', 'cetak'],
16
+ weak: ['process', 'proses', 'compress', 'extract', 'ekstrak', 'open app', 'launch']
17
+ },
18
+ advanced: {
19
+ strong: ['pdf', 'forge a tool', 'custom tool', 'plugin', 'plugins', 'custom plugin', 'subagent', 'sub-agent', 'parallel agent'],
20
+ weak: ['symbols', 'ast', 'refactor', 'orchestrate', 'swarm']
21
+ }
24
22
  };
25
-
26
23
  const STRONG_WEIGHT = 3;
27
24
  const WEAK_WEIGHT = 1;
28
25
  const PACK_THRESHOLD = 3;
29
-
30
26
  // ---------------------------------------------------------------------------
31
27
  // Task archetypes. Each one carries the phase plan the agent should follow and
32
28
  // the tools that matter in each phase.
33
29
  // ---------------------------------------------------------------------------
34
-
35
30
  const ARCHETYPES = {
36
- integrate: {
37
- label: 'Build an application that integrates live external data',
38
- match: {
39
- strong: ['realtime', 'real-time', 'real time', 'api', 'rest', 'webhook', 'integration', 'integrasi', 'sync', 'sinkron', 'automation', 'automasi', 'otomasi', 'automate', 'otomatis', 'booking', 'tiket', 'ticket', 'calendar', 'kalender', 'jadwal', 'schedule', 'payment', 'pembayaran'],
40
- weak: ['live', 'stream', 'fetch', 'data', 'third party', 'oauth', 'token']
41
- },
42
- phases: [
43
- { name: 'Research the data sources FIRST', tools: ['web_search', 'web_fetch'], note: 'Identify the exact official REST APIs, their auth model, rate limits and response shape before writing any code. Prefer documented JSON APIs over browser scraping — reading JSON always beats reading DOM.' },
44
- { name: 'Verify access and versions', tools: ['sysRun', 'web_fetch'], note: 'Probe one real endpoint and check current package versions with `npm view <pkg> version` before pinning anything. Never assume a version from memory.' },
45
- { name: 'Scaffold', tools: ['fs_write', 'sysRun'], note: 'Create the project structure and dependency manifest.' },
46
- { name: 'Implement', tools: ['fs_read', 'fs_edit', 'MultiEdit'], note: 'Build the integration layer first, the UI second. Handle auth failures and rate limits explicitly.' },
47
- { name: 'Verify', tools: ['code_verify_syntax', 'sysRun'], note: 'Run the build, lint and type-check. Exercise the live data path end to end, not just the happy path.' }
48
- ]
49
- },
50
- build_app: {
51
- label: 'Build a new application from scratch',
52
- match: {
53
- strong: ['build me', 'buatkan', 'buat aku', 'create a', 'make me', 'bikin', 'develop a', 'new app', 'from scratch'],
54
- weak: ['app', 'aplikasi', 'project', 'proyek', 'tool', 'system', 'sistem']
31
+ integrate: {
32
+ label: 'Build an application that integrates live external data',
33
+ match: {
34
+ strong: ['realtime', 'real-time', 'real time', 'api', 'rest', 'webhook', 'integration', 'integrasi', 'sync', 'sinkron', 'automation', 'automasi', 'otomasi', 'automate', 'otomatis', 'booking', 'tiket', 'ticket', 'calendar', 'kalender', 'jadwal', 'schedule', 'payment', 'pembayaran'],
35
+ weak: ['live', 'stream', 'fetch', 'data', 'third party', 'oauth', 'token']
36
+ },
37
+ phases: [
38
+ { name: 'Research the data sources FIRST', tools: ['web_search', 'web_fetch'], note: 'Identify the exact official REST APIs, their auth model, rate limits and response shape before writing any code. Prefer documented JSON APIs over browser scraping — reading JSON always beats reading DOM.' },
39
+ { name: 'Verify access and versions', tools: ['sysRun', 'web_fetch'], note: 'Probe one real endpoint and check current package versions with `npm view <pkg> version` before pinning anything. Never assume a version from memory.' },
40
+ { name: 'Scaffold', tools: ['fs_write', 'sysRun'], note: 'Create the project structure and dependency manifest.' },
41
+ { name: 'Implement', tools: ['fs_read', 'fs_edit', 'MultiEdit'], note: 'Build the integration layer first, the UI second. Handle auth failures and rate limits explicitly.' },
42
+ { name: 'Verify', tools: ['code_verify_syntax', 'sysRun'], note: 'Run the build, lint and type-check. Exercise the live data path end to end, not just the happy path.' }
43
+ ]
55
44
  },
56
- phases: [
57
- { name: 'Plan', tools: ['TodoWrite'], note: 'Break the build into atomic, verifiable steps before touching the filesystem.' },
58
- { name: 'Scaffold', tools: ['fs_write', 'sysRun'], note: 'Create structure and install dependencies at verified current versions.' },
59
- { name: 'Implement', tools: ['fs_write', 'fs_edit', 'MultiEdit'], note: 'Write runnable code with every import and dependency present.' },
60
- { name: 'Verify', tools: ['code_verify_syntax', 'sysRun'], note: 'Build, lint and run it. Never assume code works.' }
61
- ]
62
- },
63
- debug: {
64
- label: 'Diagnose and fix a defect in existing code',
65
- match: {
66
- strong: ['bug', 'error', 'crash', 'fix', 'perbaiki', 'benerin', 'broken', 'rusak', 'not working', 'gagal', 'failing', 'exception', 'stack trace', 'regression'],
67
- weak: ['issue', 'masalah', 'problem', 'wrong', 'salah', 'debug']
45
+ build_app: {
46
+ label: 'Build a new application from scratch',
47
+ match: {
48
+ strong: ['build me', 'buatkan', 'buat aku', 'create a', 'make me', 'bikin', 'develop a', 'new app', 'from scratch'],
49
+ weak: ['app', 'aplikasi', 'project', 'proyek', 'tool', 'system', 'sistem']
50
+ },
51
+ phases: [
52
+ { name: 'Plan', tools: ['TodoWrite'], note: 'Break the build into atomic, verifiable steps before touching the filesystem.' },
53
+ { name: 'Scaffold', tools: ['fs_write', 'sysRun'], note: 'Create structure and install dependencies at verified current versions.' },
54
+ { name: 'Implement', tools: ['fs_write', 'fs_edit', 'MultiEdit'], note: 'Write runnable code with every import and dependency present.' },
55
+ { name: 'Verify', tools: ['code_verify_syntax', 'sysRun'], note: 'Build, lint and run it. Never assume code works.' }
56
+ ]
68
57
  },
69
- phases: [
70
- { name: 'Reproduce', tools: ['sysRun', 'fs_read'], note: 'Reproduce the failure and capture the exact error before changing anything.' },
71
- { name: 'Locate', tools: ['code_search_ripgrep', 'codebase_search', 'fs_read'], note: 'Search several phrasings in parallel. Read the file before patching it.' },
72
- { name: 'Fix', tools: ['fs_edit', 'MultiEdit'], note: 'Make the smallest correct change. Do not touch unrelated code.' },
73
- { name: 'Verify', tools: ['sysRun', 'code_verify_syntax'], note: 'Confirm the original reproduction now passes.' }
74
- ]
75
- },
76
- research: {
77
- label: 'Investigate and report, without changing the system',
78
- match: {
79
- strong: ['explain', 'jelaskan', 'analyse', 'analyze', 'analisa', 'research', 'riset', 'compare', 'bandingkan', 'what is', 'apa itu', 'how does', 'bagaimana', 'review', 'audit'],
80
- weak: ['understand', 'pahami', 'find out', 'cari tahu', 'summarize', 'ringkas']
58
+ debug: {
59
+ label: 'Diagnose and fix a defect in existing code',
60
+ match: {
61
+ strong: ['bug', 'error', 'crash', 'fix', 'perbaiki', 'benerin', 'broken', 'rusak', 'not working', 'gagal', 'failing', 'exception', 'stack trace', 'regression'],
62
+ weak: ['issue', 'masalah', 'problem', 'wrong', 'salah', 'debug']
63
+ },
64
+ phases: [
65
+ { name: 'Reproduce', tools: ['sysRun', 'fs_read'], note: 'Reproduce the failure and capture the exact error before changing anything.' },
66
+ { name: 'Locate', tools: ['code_search_ripgrep', 'codebase_search', 'fs_read'], note: 'Search several phrasings in parallel. Read the file before patching it.' },
67
+ { name: 'Fix', tools: ['fs_edit', 'MultiEdit'], note: 'Make the smallest correct change. Do not touch unrelated code.' },
68
+ { name: 'Verify', tools: ['sysRun', 'code_verify_syntax'], note: 'Confirm the original reproduction now passes.' }
69
+ ]
81
70
  },
82
- phases: [
83
- { name: 'Gather', tools: ['code_search_ripgrep', 'fs_read', 'web_search', 'web_fetch'], note: 'Search broadly with several phrasings in parallel before concluding.' },
84
- { name: 'Report', tools: [], note: 'Answer concisely and cite exact file paths or URLs. Do not modify anything unless asked.' }
85
- ]
86
- },
87
- maintain: {
88
- label: 'Operate on files, processes or the machine itself',
89
- match: {
90
- strong: ['organize', 'rapikan', 'clean up', 'bersihkan', 'rename', 'ganti nama', 'move', 'pindahkan', 'backup', 'cadangkan', 'delete', 'hapus', 'install', 'deploy', 'commit', 'merge'],
91
- weak: ['folder', 'directory', 'file', 'berkas', 'desktop', 'downloads']
71
+ research: {
72
+ label: 'Investigate and report, without changing the system',
73
+ match: {
74
+ strong: ['explain', 'jelaskan', 'analyse', 'analyze', 'analisa', 'research', 'riset', 'compare', 'bandingkan', 'what is', 'apa itu', 'how does', 'bagaimana', 'review', 'audit'],
75
+ weak: ['understand', 'pahami', 'find out', 'cari tahu', 'summarize', 'ringkas']
76
+ },
77
+ phases: [
78
+ { name: 'Gather', tools: ['code_search_ripgrep', 'fs_read', 'web_search', 'web_fetch'], note: 'Search broadly with several phrasings in parallel before concluding.' },
79
+ { name: 'Report', tools: [], note: 'Answer concisely and cite exact file paths or URLs. Do not modify anything unless asked.' }
80
+ ]
92
81
  },
93
- phases: [
94
- { name: 'Survey', tools: ['fs_list', 'fs_find'], note: 'List and count what you are about to touch, and report it, before acting.' },
95
- { name: 'Act', tools: ['fs_move', 'fs_write', 'sysRun'], note: 'Prefer reversible operations. Flag anything destructive for approval.' },
96
- { name: 'Confirm', tools: ['fs_list'], note: 'Verify the end state matches what the user asked for.' }
97
- ]
98
- }
82
+ maintain: {
83
+ label: 'Operate on files, processes or the machine itself',
84
+ match: {
85
+ strong: ['organize', 'rapikan', 'clean up', 'bersihkan', 'rename', 'ganti nama', 'move', 'pindahkan', 'backup', 'cadangkan', 'delete', 'hapus', 'install', 'deploy', 'commit', 'merge'],
86
+ weak: ['folder', 'directory', 'file', 'berkas', 'desktop', 'downloads']
87
+ },
88
+ phases: [
89
+ { name: 'Survey', tools: ['fs_list', 'fs_find'], note: 'List and count what you are about to touch, and report it, before acting.' },
90
+ { name: 'Act', tools: ['fs_move', 'fs_write', 'sysRun'], note: 'Prefer reversible operations. Flag anything destructive for approval.' },
91
+ { name: 'Confirm', tools: ['fs_list'], note: 'Verify the end state matches what the user asked for.' }
92
+ ]
93
+ }
99
94
  };
100
-
101
95
  const ARCHETYPE_NAMES = Object.keys(ARCHETYPES);
102
-
103
96
  // ---------------------------------------------------------------------------
104
97
  // Scoring
105
98
  // ---------------------------------------------------------------------------
106
-
107
99
  function scoreSignals(text, signals) {
108
- let score = 0;
109
- const hits = [];
110
-
111
- for (const term of signals.strong || []) {
112
- if (text.includes(term)) {
113
- score += STRONG_WEIGHT;
114
- hits.push(term);
100
+ let score = 0;
101
+ const hits = [];
102
+ for (const term of signals.strong || []) {
103
+ if (text.includes(term)) {
104
+ score += STRONG_WEIGHT;
105
+ hits.push(term);
106
+ }
115
107
  }
116
- }
117
- for (const term of signals.weak || []) {
118
- // Word-boundary check for short weak terms so 'ui' cannot match 'build'
119
- // and 'race' cannot match 'trace' — the exact class of false positive that
120
- // used to drag ordinary tasks into game and web enforcement.
121
- const pattern = new RegExp(`(^|[^a-z0-9])${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}($|[^a-z0-9])`, 'i');
122
- if (pattern.test(text)) {
123
- score += WEAK_WEIGHT;
124
- hits.push(term);
108
+ for (const term of signals.weak || []) {
109
+ // Word-boundary check for short weak terms so 'ui' cannot match 'build'
110
+ // and 'race' cannot match 'trace' — the exact class of false positive that
111
+ // used to drag ordinary tasks into game and web enforcement.
112
+ const pattern = new RegExp(`(^|[^a-z0-9])${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}($|[^a-z0-9])`, 'i');
113
+ if (pattern.test(text)) {
114
+ score += WEAK_WEIGHT;
115
+ hits.push(term);
116
+ }
125
117
  }
126
- }
127
-
128
- return { score, hits };
118
+ return { score, hits };
129
119
  }
130
-
131
120
  /**
132
121
  * Analyse a request into the shape of the work it implies.
133
122
  *
134
- * @param {string} prompt
135
- * @param {object} workspaceContext Optional perception output.
136
- * @returns {{
137
- * archetype: string,
138
- * archetypeLabel: string,
139
- * packs: Array<{name: string, score: number}>,
140
- * phases: Array<object>,
141
- * confidence: string
142
- * }}
143
123
  */
144
124
  export function analyzeTask(prompt, workspaceContext = null) {
145
- const text = String(prompt || '').toLowerCase();
146
-
147
- // Which capability packs are worth their token cost for this request.
148
- const packScores = [];
149
- for (const packName of Object.keys(PACK_SIGNALS)) {
150
- const { score } = scoreSignals(text, PACK_SIGNALS[packName]);
151
- if (score >= PACK_THRESHOLD) {
152
- packScores.push({ name: packName, score });
125
+ const text = String(prompt || '').toLowerCase();
126
+ // Which capability packs are worth their token cost for this request.
127
+ const packScores = [];
128
+ for (const packName of Object.keys(PACK_SIGNALS)) {
129
+ const signals = PACK_SIGNALS[packName];
130
+ if (!signals)
131
+ continue;
132
+ const { score } = scoreSignals(text, signals);
133
+ if (score >= PACK_THRESHOLD) {
134
+ packScores.push({ name: packName, score });
135
+ }
153
136
  }
154
- }
155
-
156
- // Workspace evidence outranks keywords: a Godot project on disk is far more
157
- // reliable than any phrasing in the prompt.
158
- if (workspaceContext) {
159
- const signals = [
160
- workspaceContext.framework,
161
- workspaceContext.projectType,
162
- workspaceContext.designSystem,
163
- Array.isArray(workspaceContext.keyFiles) ? workspaceContext.keyFiles.join(' ') : ''
164
- ].filter(Boolean).join(' ').toLowerCase();
165
-
166
- const bump = (name, amount) => {
167
- const existing = packScores.find(p => p.name === name);
168
- if (existing) existing.score += amount;
169
- else packScores.push({ name, score: amount });
170
- };
171
-
172
- if (/godot|project\.godot|unity|\.tscn/.test(signals)) bump('game', 5);
173
- if (/react|vue|svelte|next|nuxt|astro|angular|tailwind|vite|index\.html/.test(signals)) bump('web', 4);
174
- }
175
-
176
- packScores.sort((a, b) => b.score - a.score);
177
-
178
- // Which archetype the work follows.
179
- let bestArchetype = null;
180
- let bestScore = 0;
181
- for (const name of ARCHETYPE_NAMES) {
182
- const { score } = scoreSignals(text, ARCHETYPES[name].match);
183
- if (score > bestScore) {
184
- bestScore = score;
185
- bestArchetype = name;
137
+ // Workspace evidence outranks keywords: a Godot project on disk is far more
138
+ // reliable than any phrasing in the prompt.
139
+ if (workspaceContext) {
140
+ const signals = [
141
+ workspaceContext.framework,
142
+ workspaceContext.projectType,
143
+ workspaceContext.designSystem,
144
+ Array.isArray(workspaceContext.keyFiles) ? workspaceContext.keyFiles.join(' ') : ''
145
+ ].filter(Boolean).join(' ').toLowerCase();
146
+ const bump = (name, amount) => {
147
+ const existing = packScores.find(p => p.name === name);
148
+ if (existing)
149
+ existing.score += amount;
150
+ else
151
+ packScores.push({ name, score: amount });
152
+ };
153
+ if (/godot|project\.godot|unity|\.tscn/.test(signals))
154
+ bump('game', 5);
155
+ if (/react|vue|svelte|next|nuxt|astro|angular|tailwind|vite|index\.html/.test(signals))
156
+ bump('web', 4);
157
+ }
158
+ packScores.sort((a, b) => b.score - a.score);
159
+ // Which archetype the work follows.
160
+ let bestArchetype = null;
161
+ let bestScore = 0;
162
+ for (const name of ARCHETYPE_NAMES) {
163
+ const definition = ARCHETYPES[name];
164
+ if (!definition)
165
+ continue;
166
+ const { score } = scoreSignals(text, definition.match);
167
+ if (score > bestScore) {
168
+ bestScore = score;
169
+ bestArchetype = name;
170
+ }
186
171
  }
187
- }
188
-
189
- // A build request that also talks about live external data is an integration
190
- // job first and a build job second — that ordering is what stops the agent
191
- // scaffolding a UI before it knows whether the API even exists.
192
- const integrationScore = scoreSignals(text, ARCHETYPES.integrate.match).score;
193
- if (integrationScore >= STRONG_WEIGHT && bestArchetype === 'build_app') {
194
- bestArchetype = 'integrate';
195
- bestScore = integrationScore;
196
- }
197
-
198
- const archetype = bestArchetype || 'build_app';
199
- const confidence = bestScore >= 6 ? 'high' : (bestScore >= 3 ? 'medium' : 'low');
200
-
201
- return {
202
- archetype,
203
- archetypeLabel: ARCHETYPES[archetype].label,
204
- packs: packScores,
205
- phases: ARCHETYPES[archetype].phases,
206
- confidence
207
- };
172
+ // A build request that also talks about live external data is an integration
173
+ // job first and a build job second — that ordering is what stops the agent
174
+ // scaffolding a UI before it knows whether the API even exists.
175
+ const integrate = ARCHETYPES.integrate;
176
+ if (!integrate)
177
+ throw new Error('integrate task archetype is unavailable');
178
+ const integrationScore = scoreSignals(text, integrate.match).score;
179
+ if (integrationScore >= STRONG_WEIGHT && bestArchetype === 'build_app') {
180
+ bestArchetype = 'integrate';
181
+ bestScore = integrationScore;
182
+ }
183
+ const archetype = bestArchetype || 'build_app';
184
+ const confidence = bestScore >= 6 ? 'high' : (bestScore >= 3 ? 'medium' : 'low');
185
+ const definition = ARCHETYPES[archetype];
186
+ if (!definition)
187
+ throw new Error(`task archetype ${archetype} is unavailable`);
188
+ return {
189
+ archetype,
190
+ archetypeLabel: definition.label,
191
+ packs: packScores,
192
+ phases: definition.phases,
193
+ confidence
194
+ };
208
195
  }
209
-
210
196
  /**
211
197
  * Render the analysis as a compact brief for the system prompt.
212
198
  *
@@ -215,27 +201,22 @@ export function analyzeTask(prompt, workspaceContext = null) {
215
201
  * exploratory steps, each of which costs thousands.
216
202
  */
217
203
  export function buildTaskBrief(shape, availableToolNames) {
218
- if (!shape) return '';
219
-
220
- const available = new Set(availableToolNames || []);
221
- const lines = [];
222
-
223
- lines.push('# TASK SHAPE (analysed from your request)');
224
- lines.push(`Work type: ${shape.archetypeLabel}.`);
225
- lines.push('Follow this sequence. Do not skip ahead — each phase de-risks the next.');
226
-
227
- shape.phases.forEach((phase, index) => {
228
- const tools = (phase.tools || []).filter(name => available.has(name));
229
- const toolList = tools.length > 0 ? ` [${tools.join(', ')}]` : '';
230
- lines.push(`${index + 1}. ${phase.name}${toolList} — ${phase.note}`);
231
- });
232
-
233
- lines.push('Tools outside this list remain fully available; search for them with tool_search when a phase genuinely needs one.');
234
-
235
- return lines.join('\n');
204
+ if (!shape)
205
+ return '';
206
+ const available = new Set(availableToolNames || []);
207
+ const lines = [];
208
+ lines.push('# TASK SHAPE (analysed from your request)');
209
+ lines.push(`Work type: ${shape.archetypeLabel}.`);
210
+ lines.push('Follow this sequence. Do not skip ahead — each phase de-risks the next.');
211
+ shape.phases.forEach((phase, index) => {
212
+ const tools = (phase.tools || []).filter(name => available.has(name));
213
+ const toolList = tools.length > 0 ? ` [${tools.join(', ')}]` : '';
214
+ lines.push(`${index + 1}. ${phase.name}${toolList} — ${phase.note}`);
215
+ });
216
+ lines.push('Tools outside this list remain fully available; search for them with tool_search when a phase genuinely needs one.');
217
+ return lines.join('\n');
236
218
  }
237
-
238
219
  /** Pack names above threshold, highest score first. */
239
220
  export function packsFromShape(shape) {
240
- return shape && Array.isArray(shape.packs) ? shape.packs.map(p => p.name) : [];
221
+ return shape && Array.isArray(shape.packs) ? shape.packs.map(p => p.name) : [];
241
222
  }