@causyne/agent 1.10.13 → 1.10.18
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/docs/ARCHITECTURE.md +118 -17
- package/docs/SECURITY_REVIEW.md +13 -1
- package/docs/THIRD_PARTY_NOTICES.md +46 -10
- package/package.json +10 -4
- package/src/agentLoop.js +123 -21
- package/src/agentProfiles.js +135 -0
- package/src/agentProfiles.ts +143 -0
- package/src/approvalGrants.js +17 -35
- package/src/approvalGrants.ts +54 -0
- package/src/auditLog.d.ts +3 -0
- package/src/browser/browserRuntime.js +88 -0
- package/src/browser/browserRuntime.ts +107 -0
- package/src/browser/causyneBrowser.js +495 -0
- package/src/browser/causyneBrowser.ts +594 -0
- package/src/browser/searchProviders.js +156 -0
- package/src/browser/searchProviders.ts +156 -0
- package/src/browser/snapshotWindow.js +28 -0
- package/src/browser/snapshotWindow.ts +40 -0
- package/src/browser/urlPolicy.js +89 -0
- package/src/browser/urlPolicy.ts +89 -0
- package/src/checkpoints.js +19 -6
- package/src/cliMode.js +13 -1
- package/src/commands.js +37 -8
- package/src/config.d.ts +2 -0
- package/src/decisionPolicy.js +68 -0
- package/src/decisionPolicy.ts +106 -0
- package/src/hooks.js +121 -107
- package/src/hooks.ts +193 -0
- package/src/implementationPlan.js +50 -0
- package/src/implementationPlan.ts +59 -0
- package/src/mcp/mcpClient.js +520 -132
- package/src/mcp/mcpClient.ts +584 -0
- package/src/mcp/mcpRegistry.js +399 -185
- package/src/mcp/mcpRegistry.ts +451 -0
- package/src/planCache.js +473 -450
- package/src/planCache.ts +840 -0
- package/src/plugins/pluginManager.js +40 -5
- package/src/plugins/pluginSkills.js +84 -0
- package/src/plugins/pluginSkills.ts +106 -0
- package/src/provisioning/capabilityProvisioner.d.ts +10 -0
- package/src/provisioning/capabilityProvisioner.js +5 -0
- package/src/provisioning/capabilityTools.js +2 -1
- package/src/sandbox.js +32 -2
- package/src/sessionJournal.js +471 -0
- package/src/sessionJournal.ts +496 -0
- package/src/sessionStore.js +3 -279
- package/src/skillsManager.js +18 -0
- package/src/subagentJournal.js +215 -0
- package/src/subagentJournal.ts +254 -0
- package/src/taskShape.js +179 -198
- package/src/taskShape.ts +259 -0
- package/src/toolPacks.js +7 -5
- package/src/tools/browserAutomator.js +45 -241
- package/src/tools/browserAutomator.ts +57 -0
- package/src/tools/manageLocalAiDependencies.js +1 -1
- package/src/tools/registry.js +4 -3
- package/src/tools/requestDecision.js +87 -0
- package/src/tools/requestDecision.ts +107 -0
- package/src/tools/subagentOrchestrator.js +73 -12
- package/src/tools/updatePlan.js +37 -84
- package/src/tools/updatePlan.ts +48 -0
- package/src/tools/webFetch.js +183 -68
- package/src/tools/webFetch.ts +185 -0
- package/src/tools/webSearch.js +50 -75
- package/src/tools/webSearch.ts +56 -0
- package/src/tui/ask.js +41 -1
- package/src/tui/chatRenderer.js +28 -0
- package/src/tui/dialogs.js +91 -0
- package/src/tui/prompt.js +32 -3
- package/src/tui/toolDisplay.js +0 -1
- package/src/types/contracts.js +8 -0
- package/src/types/contracts.ts +263 -0
- package/src/utils/paths.d.ts +3 -0
- package/src/version.d.ts +1 -0
- package/src/mcp/jsonRpc.js +0 -165
- package/src/tools/todoWrite.js +0 -75
package/src/taskShape.js
CHANGED
|
@@ -1,212 +1,198 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
const PACK_SIGNALS = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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: ['update_plan'], note: 'Create a visible atomic implementation plan 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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
bestArchetype
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
lines.push(
|
|
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
|
-
|
|
221
|
+
return shape && Array.isArray(shape.packs) ? shape.packs.map(p => p.name) : [];
|
|
241
222
|
}
|