@compilr-dev/sdk 0.17.18 → 0.18.1
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.
|
@@ -70,7 +70,9 @@ export function createCanvasTools(config) {
|
|
|
70
70
|
'Bind params in your HTML via CSS custom properties var(--param), [data-bind="param"] text, and ' +
|
|
71
71
|
'[data-show="param"] visibility; for computed updates define window.applyParams(values) in a <script>. ' +
|
|
72
72
|
'Omit canvas_id to create. To EDIT an existing canvas, prefer canvas_edit (str_replace) — it avoids ' +
|
|
73
|
-
're-sending the whole document; only use canvas_write with canvas_id for a full intentional replace.'
|
|
73
|
+
're-sending the whole document; only use canvas_write with canvas_id for a full intentional replace. ' +
|
|
74
|
+
'Before creating a NEW canvas, gather the essentials from the user (purpose, type, key content, style) ' +
|
|
75
|
+
'with ask_user unless they already specified them — authoring on guessed requirements wastes a full pass.',
|
|
74
76
|
inputSchema: {
|
|
75
77
|
type: 'object',
|
|
76
78
|
properties: {
|
|
@@ -35,7 +35,13 @@ export const canvasSkill = defineSkill({
|
|
|
35
35
|
|
|
36
36
|
## STEPS
|
|
37
37
|
|
|
38
|
-
1. **
|
|
38
|
+
1. **Collect the essentials from the user FIRST — this is the standard approach.** A canvas is an expensive authoring pass; building it on guessed requirements wastes that pass and lands wide of what the user wanted. So before you author, use **\`ask_user\`** (batch several questions into ONE call) to gather what the canvas needs:
|
|
39
|
+
- **Purpose & audience** — what is it for, who reads it?
|
|
40
|
+
- **Canvas type** — infographic, carousel, or board? (Offer the choice unless obvious.)
|
|
41
|
+
- **Key content / data** — the actual points, numbers, sections, or slides to include. Don't invent data.
|
|
42
|
+
- **Style / brand** — theme-matched (default) or a specific palette/brand?
|
|
43
|
+
For open design directions (which layout, which visual approach), present concrete options with **\`propose_alternatives\`** instead of guessing.
|
|
44
|
+
**Skip questions the user already answered**, and if they gave full detail or explicitly said "just make it / surprise me / your call", go straight to authoring. Keep it to ONE focused round — a short question batch, never a long interview — then build.
|
|
39
45
|
|
|
40
46
|
2. **Build the canvas in small steps — never one giant tool call.** A canvas is HTML/SVG you emit as tool arguments; a single very large \`canvas_write\` can overrun the output limit, get cut off mid-arguments, and fail. So author it incrementally, and emit each tool call immediately with NO prose preamble (narration competes with the HTML for the same output budget):
|
|
41
47
|
- **2a. First \`canvas_write\`** (type, title, html) = a COMPACT skeleton: the \`<style>\` block, the overall layout, and just the first section or heading. This is the ONLY way to create a canvas — describing it in chat does nothing.
|
|
@@ -40,31 +40,57 @@ export interface ConsultResult {
|
|
|
40
40
|
* (target run exceeded max iterations, transport error, etc.). */
|
|
41
41
|
error?: string;
|
|
42
42
|
}
|
|
43
|
+
/** Default wall-clock cap for a whole consult sub-run (ms). */
|
|
44
|
+
export declare const DEFAULT_CONSULT_TIMEOUT_MS = 180000;
|
|
45
|
+
/**
|
|
46
|
+
* Options passed to `onConsult` alongside the question. Additive — hosts
|
|
47
|
+
* that predate the timeout work can ignore `signal` and keep compiling,
|
|
48
|
+
* but they will NOT get the hang protection until they forward it into
|
|
49
|
+
* their target `agent.run(..., { signal })`.
|
|
50
|
+
*/
|
|
51
|
+
export interface ConsultRunOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Aborts when the wall-clock timeout fires (or the host links its own
|
|
54
|
+
* borrower signal to it). Implementations MUST forward this into the
|
|
55
|
+
* target agent's run so an expired consult actually stops the sub-run
|
|
56
|
+
* instead of orphaning it.
|
|
57
|
+
*/
|
|
58
|
+
signal: AbortSignal;
|
|
59
|
+
}
|
|
43
60
|
/**
|
|
44
61
|
* Configuration for `createConsultTool`.
|
|
45
62
|
*
|
|
46
63
|
* `onConsult` is intentionally platform-shaped — each host runs sub-agents
|
|
47
64
|
* differently (CLI's REPL sub-context, Desktop's agent-manager, in-process
|
|
48
|
-
* Agent.run). The SDK owns validation
|
|
49
|
-
* owns the actual run.
|
|
65
|
+
* Agent.run). The SDK owns validation, message format, and the wall-clock
|
|
66
|
+
* timeout; the platform owns the actual run.
|
|
50
67
|
*/
|
|
51
68
|
export interface ConsultToolConfig {
|
|
52
69
|
/** The team instance — used to validate target agent membership. */
|
|
53
70
|
team: AgentTeam;
|
|
54
71
|
/** The borrowing agent's ID. */
|
|
55
72
|
currentAgentId: string;
|
|
73
|
+
/**
|
|
74
|
+
* Wall-clock cap for the whole consult sub-run, in ms. The SDK arms a
|
|
75
|
+
* timer, aborts `signal` on expiry, and returns a recoverable timeout
|
|
76
|
+
* error to the borrower's LLM. Defaults to {@link DEFAULT_CONSULT_TIMEOUT_MS}.
|
|
77
|
+
*/
|
|
78
|
+
timeoutMs?: number;
|
|
56
79
|
/**
|
|
57
80
|
* Platform-specific consult handler. Called after validation passes.
|
|
58
81
|
* Should:
|
|
59
82
|
* 1. Synthesise the question message (use `buildConsultQuestionMessage`).
|
|
60
|
-
* 2. Run the target agent against that message
|
|
61
|
-
*
|
|
83
|
+
* 2. Run the target agent against that message, **history-aware** (the
|
|
84
|
+
* target's real agent/history — NOT a throwaway or snapshot-restored
|
|
85
|
+
* run), forwarding `opts.signal` into the run so the timeout can
|
|
86
|
+
* abort it.
|
|
62
87
|
* 3. Return the target's final response text.
|
|
63
88
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
89
|
+
* As of consult v2 the target DOES remember the exchange — the historical
|
|
90
|
+
* "transient / history not mutated" contract is removed. See
|
|
91
|
+
* `consult-persistent-conversation-spec.md`.
|
|
66
92
|
*/
|
|
67
|
-
onConsult: (targetAgentId: string, question: string, context: string | undefined) => Promise<{
|
|
93
|
+
onConsult: (targetAgentId: string, question: string, context: string | undefined, opts: ConsultRunOptions) => Promise<{
|
|
68
94
|
answer: string;
|
|
69
95
|
error?: string;
|
|
70
96
|
}>;
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
* /workspace/project-docs/00-requirements/compilr-dev-sdk/consult-tool-spec.md
|
|
22
22
|
*/
|
|
23
23
|
import { defineTool } from '@compilr-dev/agents';
|
|
24
|
+
/** Default wall-clock cap for a whole consult sub-run (ms). */
|
|
25
|
+
export const DEFAULT_CONSULT_TIMEOUT_MS = 180_000;
|
|
24
26
|
// =============================================================================
|
|
25
27
|
// Canonical question-message format
|
|
26
28
|
// =============================================================================
|
|
@@ -63,10 +65,12 @@ export function buildConsultQuestionMessage(input) {
|
|
|
63
65
|
*/
|
|
64
66
|
export function createConsultTool(config) {
|
|
65
67
|
const { team, currentAgentId, onConsult } = config;
|
|
68
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_CONSULT_TIMEOUT_MS;
|
|
66
69
|
return defineTool({
|
|
67
70
|
name: 'consult',
|
|
68
|
-
description: 'Ask
|
|
69
|
-
'
|
|
71
|
+
description: 'Ask a teammate a focused question and continue your own work. ' +
|
|
72
|
+
'They answer using their own context and remember the exchange, so a consult is a real interaction in their conversation — not a throwaway. ' +
|
|
73
|
+
'Use this when you need a quick opinion or expertise from a teammate without giving up the conversation. ' +
|
|
70
74
|
'Unlike handoff, you keep ownership — the target answers and control returns to you. ' +
|
|
71
75
|
'Example: $arch can consult $pm on scope, get an answer, and continue designing.',
|
|
72
76
|
inputSchema: {
|
|
@@ -123,8 +127,19 @@ export function createConsultTool(config) {
|
|
|
123
127
|
error: `Agent "${targetId}" not found in team. Available specialists: ${available || '(none)'}`,
|
|
124
128
|
};
|
|
125
129
|
}
|
|
130
|
+
// Wall-clock cap for the whole sub-run. The SDK owns this so BOTH
|
|
131
|
+
// hosts inherit the hang fix without duplicating timeout logic. On
|
|
132
|
+
// expiry we abort the signal (the host forwards it into the target
|
|
133
|
+
// run) and hand the borrower's LLM a recoverable error rather than
|
|
134
|
+
// hanging "running" forever — the failure mode observed in testing.
|
|
135
|
+
const ac = new AbortController();
|
|
136
|
+
const timer = setTimeout(() => {
|
|
137
|
+
ac.abort(new Error('consult-timeout'));
|
|
138
|
+
}, timeoutMs);
|
|
126
139
|
try {
|
|
127
|
-
const { answer, error } = await onConsult(targetId, question, context
|
|
140
|
+
const { answer, error } = await onConsult(targetId, question, context, {
|
|
141
|
+
signal: ac.signal,
|
|
142
|
+
});
|
|
128
143
|
if (error) {
|
|
129
144
|
return { success: false, error };
|
|
130
145
|
}
|
|
@@ -132,11 +147,23 @@ export function createConsultTool(config) {
|
|
|
132
147
|
return { success: true, result };
|
|
133
148
|
}
|
|
134
149
|
catch (err) {
|
|
150
|
+
if (ac.signal.aborted) {
|
|
151
|
+
const seconds = String(Math.round(timeoutMs / 1000));
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
error: `$${targetId} didn't respond in time (${seconds}s). ` +
|
|
155
|
+
'It may have a pending question — the request was surfaced in your chat. ' +
|
|
156
|
+
'You can try consulting again, ask the user, or proceed without it.',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
135
159
|
return {
|
|
136
160
|
success: false,
|
|
137
161
|
error: `Consult failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
138
162
|
};
|
|
139
163
|
}
|
|
164
|
+
finally {
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
}
|
|
140
167
|
},
|
|
141
168
|
});
|
|
142
169
|
}
|