@compilr-dev/sdk 0.23.0 → 0.23.2
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/dist/team/team-agent.d.ts +54 -0
- package/dist/team/team-agent.js +104 -0
- package/package.json +1 -1
|
@@ -13,6 +13,38 @@ import type { ModelTier } from '../models/index.js';
|
|
|
13
13
|
import type { ProviderType } from '../config.js';
|
|
14
14
|
import type { SharedContextManager } from './shared-context.js';
|
|
15
15
|
import type { CustomAgentDefinition } from './custom-agents.js';
|
|
16
|
+
/**
|
|
17
|
+
* Strip the composite a pre-0.23 `fromRole()` stored, so it is not composed a second time.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ WITHOUT THIS, EVERY ROLE AGENT CREATED BEFORE 0.23 SENDS ITS ROLE PROMPT TWICE. The first
|
|
20
|
+
* cut of this migration skipped role agents, reasoning that an edited stored prompt is
|
|
21
|
+
* indistinguishable from an unedited one so removing anything risks the user's text. True, and
|
|
22
|
+
* beside the point: the generated sections now compose on top of whatever is stored, so leaving
|
|
23
|
+
* the legacy copy in place duplicates it. Measured on a real `$arch`: 13,024 characters instead
|
|
24
|
+
* of ~6,900, with the role header and capability blurb appearing twice each.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ AND IT CANNOT WORK BY RECONSTRUCTION. The second cut rebuilt the old composite exactly and
|
|
27
|
+
* stripped it on an exact match. That matched 6 of 16 real agents. The capability blurb lists
|
|
28
|
+
* the profile's tool GROUPS as they stood on the day the agent was created — a `qa` agent saved
|
|
29
|
+
* before the profile gained `consult` carries a blurb nine characters shorter than today's, and
|
|
30
|
+
* no amount of care reproduces a list that has since changed.
|
|
31
|
+
*
|
|
32
|
+
* So this anchors on the two ends, which are stable, and ignores the drifting middle entirely:
|
|
33
|
+
*
|
|
34
|
+
* - the FIRST LINE must be the first line of generated content (the role header, or the
|
|
35
|
+
* guidance heading for roles with no role prompt of their own), and
|
|
36
|
+
* - the CURRENT guidance block must appear — it is always last, and unlike the tool blurb it
|
|
37
|
+
* is not derived from mutable configuration.
|
|
38
|
+
*
|
|
39
|
+
* Everything up to the end of that guidance is generated; whatever follows is the user's and is
|
|
40
|
+
* kept. If either anchor is missing the value is returned untouched, so hand-written text and a
|
|
41
|
+
* future rewording both fail safe: the agent keeps its duplication until someone clears the box,
|
|
42
|
+
* and nobody loses text they wrote.
|
|
43
|
+
*
|
|
44
|
+
* @returns the user's own remainder, `undefined` if the stored value was entirely generated, or
|
|
45
|
+
* `stored` unchanged when it is not recognisably a legacy composite.
|
|
46
|
+
*/
|
|
47
|
+
export declare function splitLegacyRolePrompt(stored: string, role: AgentRole): string | undefined;
|
|
16
48
|
/**
|
|
17
49
|
* The editable subset of an agent.
|
|
18
50
|
*
|
|
@@ -240,6 +272,28 @@ export declare class TeamAgent {
|
|
|
240
272
|
* `thought_signature`) that do not transfer across a rebuild.
|
|
241
273
|
*/
|
|
242
274
|
private stashForRebuild;
|
|
275
|
+
/**
|
|
276
|
+
* A turn is in flight: the host has called `run()`/`stream()` and is awaiting it.
|
|
277
|
+
*
|
|
278
|
+
* ⚠️ HOSTS MUST BRACKET EVERY RUN, IN A `finally`. `TeamAgent` does not own the run call —
|
|
279
|
+
* the host holds the unwrapped `Agent` — so it cannot observe this for itself.
|
|
280
|
+
*/
|
|
281
|
+
private turnStartedAt;
|
|
282
|
+
/**
|
|
283
|
+
* How long a turn may be in flight before this stops believing the host.
|
|
284
|
+
*
|
|
285
|
+
* ⚠️ A FLAG THAT ONLY EVER GETS SET IS WORSE THAN THE BUG IT GUARDS. A host that throws past
|
|
286
|
+
* its `endTurn()` would block every edit to that agent for the life of the process, with no
|
|
287
|
+
* way back short of a restart. Going stale bounds that to one window; the edit that slips
|
|
288
|
+
* through after it is the same edit that would have been allowed before this guard existed.
|
|
289
|
+
*/
|
|
290
|
+
private static readonly TURN_STALE_AFTER_MS;
|
|
291
|
+
/** Mark the start of a turn. Pair with `endTurn()` in a `finally`. */
|
|
292
|
+
beginTurn(): void;
|
|
293
|
+
/** Mark the end of a turn, however it ended — completed, aborted or thrown. */
|
|
294
|
+
endTurn(): void;
|
|
295
|
+
/** Whether a turn is in flight and recent enough to still be believed. */
|
|
296
|
+
get isTurnInFlight(): boolean;
|
|
243
297
|
/**
|
|
244
298
|
* Apply an edit to this agent in place.
|
|
245
299
|
*
|
package/dist/team/team-agent.js
CHANGED
|
@@ -11,6 +11,58 @@ import { ROLE_METADATA } from './types.js';
|
|
|
11
11
|
import { getModelForTier, getModelContextWindow } from '../models/index.js';
|
|
12
12
|
import { generateCustomAgentScaffold, getCustomAgentToolFilter, splitLegacyCustomPrompt, } from './custom-agents.js';
|
|
13
13
|
import { getToolsForProfile, PROFILE_INFO, generateToolAwarenessPrompt, generateCoordinatorGuidance, generateSpecialistGuidance, } from './tool-config.js';
|
|
14
|
+
/**
|
|
15
|
+
* Strip the composite a pre-0.23 `fromRole()` stored, so it is not composed a second time.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ WITHOUT THIS, EVERY ROLE AGENT CREATED BEFORE 0.23 SENDS ITS ROLE PROMPT TWICE. The first
|
|
18
|
+
* cut of this migration skipped role agents, reasoning that an edited stored prompt is
|
|
19
|
+
* indistinguishable from an unedited one so removing anything risks the user's text. True, and
|
|
20
|
+
* beside the point: the generated sections now compose on top of whatever is stored, so leaving
|
|
21
|
+
* the legacy copy in place duplicates it. Measured on a real `$arch`: 13,024 characters instead
|
|
22
|
+
* of ~6,900, with the role header and capability blurb appearing twice each.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ AND IT CANNOT WORK BY RECONSTRUCTION. The second cut rebuilt the old composite exactly and
|
|
25
|
+
* stripped it on an exact match. That matched 6 of 16 real agents. The capability blurb lists
|
|
26
|
+
* the profile's tool GROUPS as they stood on the day the agent was created — a `qa` agent saved
|
|
27
|
+
* before the profile gained `consult` carries a blurb nine characters shorter than today's, and
|
|
28
|
+
* no amount of care reproduces a list that has since changed.
|
|
29
|
+
*
|
|
30
|
+
* So this anchors on the two ends, which are stable, and ignores the drifting middle entirely:
|
|
31
|
+
*
|
|
32
|
+
* - the FIRST LINE must be the first line of generated content (the role header, or the
|
|
33
|
+
* guidance heading for roles with no role prompt of their own), and
|
|
34
|
+
* - the CURRENT guidance block must appear — it is always last, and unlike the tool blurb it
|
|
35
|
+
* is not derived from mutable configuration.
|
|
36
|
+
*
|
|
37
|
+
* Everything up to the end of that guidance is generated; whatever follows is the user's and is
|
|
38
|
+
* kept. If either anchor is missing the value is returned untouched, so hand-written text and a
|
|
39
|
+
* future rewording both fail safe: the agent keeps its duplication until someone clears the box,
|
|
40
|
+
* and nobody loses text they wrote.
|
|
41
|
+
*
|
|
42
|
+
* @returns the user's own remainder, `undefined` if the stored value was entirely generated, or
|
|
43
|
+
* `stored` unchanged when it is not recognisably a legacy composite.
|
|
44
|
+
*/
|
|
45
|
+
export function splitLegacyRolePrompt(stored, role) {
|
|
46
|
+
/*
|
|
47
|
+
⚠️ TYPED AS TOTAL, NOT TOTAL AT RUNTIME. `role` is `AgentRole` to the compiler but arrives
|
|
48
|
+
from a JSON file on disk, so it can be any string — a role removed in a later version, or one
|
|
49
|
+
a fixture invented. The explicit `| undefined` is what makes the guard below legitimate
|
|
50
|
+
rather than "always falsy": lint reasons from the type, and the type is a claim about data
|
|
51
|
+
we do not control.
|
|
52
|
+
*/
|
|
53
|
+
const byRole = ROLE_METADATA;
|
|
54
|
+
const metadata = byRole[role];
|
|
55
|
+
if (!metadata)
|
|
56
|
+
return stored;
|
|
57
|
+
const guidance = role === 'default' ? generateCoordinatorGuidance() : generateSpecialistGuidance(role);
|
|
58
|
+
const generatedFirstLine = (metadata.defaultSystemPromptAddition || guidance).split('\n')[0];
|
|
59
|
+
if (stored.split('\n')[0] !== generatedFirstLine)
|
|
60
|
+
return stored;
|
|
61
|
+
const at = stored.lastIndexOf(guidance);
|
|
62
|
+
if (at === -1)
|
|
63
|
+
return stored;
|
|
64
|
+
return stored.slice(at + guidance.length).trim() || undefined;
|
|
65
|
+
}
|
|
14
66
|
/**
|
|
15
67
|
* Estimate token count from text (approximate: ~4 chars per token)
|
|
16
68
|
*/
|
|
@@ -285,6 +337,36 @@ export class TeamAgent {
|
|
|
285
337
|
this.storedState = state;
|
|
286
338
|
this._agent = null;
|
|
287
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* A turn is in flight: the host has called `run()`/`stream()` and is awaiting it.
|
|
342
|
+
*
|
|
343
|
+
* ⚠️ HOSTS MUST BRACKET EVERY RUN, IN A `finally`. `TeamAgent` does not own the run call —
|
|
344
|
+
* the host holds the unwrapped `Agent` — so it cannot observe this for itself.
|
|
345
|
+
*/
|
|
346
|
+
turnStartedAt = null;
|
|
347
|
+
/**
|
|
348
|
+
* How long a turn may be in flight before this stops believing the host.
|
|
349
|
+
*
|
|
350
|
+
* ⚠️ A FLAG THAT ONLY EVER GETS SET IS WORSE THAN THE BUG IT GUARDS. A host that throws past
|
|
351
|
+
* its `endTurn()` would block every edit to that agent for the life of the process, with no
|
|
352
|
+
* way back short of a restart. Going stale bounds that to one window; the edit that slips
|
|
353
|
+
* through after it is the same edit that would have been allowed before this guard existed.
|
|
354
|
+
*/
|
|
355
|
+
static TURN_STALE_AFTER_MS = 10 * 60 * 1000;
|
|
356
|
+
/** Mark the start of a turn. Pair with `endTurn()` in a `finally`. */
|
|
357
|
+
beginTurn() {
|
|
358
|
+
this.turnStartedAt = Date.now();
|
|
359
|
+
}
|
|
360
|
+
/** Mark the end of a turn, however it ended — completed, aborted or thrown. */
|
|
361
|
+
endTurn() {
|
|
362
|
+
this.turnStartedAt = null;
|
|
363
|
+
}
|
|
364
|
+
/** Whether a turn is in flight and recent enough to still be believed. */
|
|
365
|
+
get isTurnInFlight() {
|
|
366
|
+
if (this.turnStartedAt === null)
|
|
367
|
+
return false;
|
|
368
|
+
return Date.now() - this.turnStartedAt < TeamAgent.TURN_STALE_AFTER_MS;
|
|
369
|
+
}
|
|
288
370
|
/**
|
|
289
371
|
* Apply an edit to this agent in place.
|
|
290
372
|
*
|
|
@@ -308,6 +390,25 @@ export class TeamAgent {
|
|
|
308
390
|
when it is not. Its `rebuilt` contribution is folded in below.
|
|
309
391
|
*/
|
|
310
392
|
const tierChanged = patch.modelTier !== undefined && patch.modelTier !== this._modelTier;
|
|
393
|
+
/*
|
|
394
|
+
⚠️ REFUSED MID-TURN, BEFORE ANYTHING IS MUTATED. A rebuild snapshots the history with
|
|
395
|
+
`serialize()` and drops the instance — but the in-flight `run()` holds its own reference
|
|
396
|
+
and finishes anyway, appending to an agent nobody will read again. Measured: the user
|
|
397
|
+
watches a reply stream in, and the rebuilt agent is handed the snapshot from BEFORE it,
|
|
398
|
+
with no memory of having said it.
|
|
399
|
+
|
|
400
|
+
Not a broken conversation — `setHistory` repairs tool pairing, so nothing 400s. It is
|
|
401
|
+
worse than that: the agent silently forgets a turn the user just watched, and the next
|
|
402
|
+
thing it says is built on a history the user can see is wrong.
|
|
403
|
+
|
|
404
|
+
Only rebuilding edits are refused. A rename or a new face is read from the roster at
|
|
405
|
+
render time and cannot tear anything, so those still apply mid-turn.
|
|
406
|
+
*/
|
|
407
|
+
if ((needsRebuild || tierChanged) && this.isTurnInFlight) {
|
|
408
|
+
throw new Error(`Agent '${this.id}' is mid-reply. Changing its prompt, model, tools or skills rebuilds ` +
|
|
409
|
+
`it and would drop the turn it is in the middle of — wait for the reply to finish, ` +
|
|
410
|
+
`or stop it, then save.`);
|
|
411
|
+
}
|
|
311
412
|
if (patch.modelTier !== undefined)
|
|
312
413
|
this.setModelTier(patch.modelTier);
|
|
313
414
|
if (patch.displayName !== undefined)
|
|
@@ -670,6 +771,9 @@ export class TeamAgent {
|
|
|
670
771
|
personality = personality ?? legacy.personality;
|
|
671
772
|
}
|
|
672
773
|
}
|
|
774
|
+
else if (data.role !== 'custom' && systemPromptAddition) {
|
|
775
|
+
systemPromptAddition = splitLegacyRolePrompt(systemPromptAddition, data.role);
|
|
776
|
+
}
|
|
673
777
|
const agent = new TeamAgent({
|
|
674
778
|
id: data.id,
|
|
675
779
|
displayName: data.displayName,
|