@telora/daemon 0.16.42 → 0.16.44
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/build-info.json +2 -2
- package/dist/directive/directive-queue.d.ts +117 -0
- package/dist/directive/directive-queue.d.ts.map +1 -0
- package/dist/directive/directive-queue.js +114 -0
- package/dist/directive/directive-queue.js.map +1 -0
- package/dist/directive/stage-tracker.d.ts +23 -0
- package/dist/directive/stage-tracker.d.ts.map +1 -0
- package/dist/directive/stage-tracker.js +27 -0
- package/dist/directive/stage-tracker.js.map +1 -0
- package/dist/directive-executor.d.ts +3 -102
- package/dist/directive-executor.d.ts.map +1 -1
- package/dist/directive-executor.js +14 -112
- package/dist/directive-executor.js.map +1 -1
- package/dist/focus-executor.d.ts.map +1 -1
- package/dist/focus-executor.js +1 -0
- package/dist/focus-executor.js.map +1 -1
- package/dist/prompt-sections/context-sections.d.ts +32 -0
- package/dist/prompt-sections/context-sections.d.ts.map +1 -0
- package/dist/prompt-sections/context-sections.js +86 -0
- package/dist/prompt-sections/context-sections.js.map +1 -0
- package/dist/prompt-sections/execution-sections.d.ts +82 -0
- package/dist/prompt-sections/execution-sections.d.ts.map +1 -0
- package/dist/prompt-sections/execution-sections.js +601 -0
- package/dist/prompt-sections/execution-sections.js.map +1 -0
- package/dist/prompt-sections/persona-sections.d.ts +133 -0
- package/dist/prompt-sections/persona-sections.d.ts.map +1 -0
- package/dist/prompt-sections/persona-sections.js +317 -0
- package/dist/prompt-sections/persona-sections.js.map +1 -0
- package/dist/prompt-sections/role-sections.d.ts +24 -0
- package/dist/prompt-sections/role-sections.d.ts.map +1 -0
- package/dist/prompt-sections/role-sections.js +78 -0
- package/dist/prompt-sections/role-sections.js.map +1 -0
- package/dist/prompt-sections/shared.d.ts +66 -0
- package/dist/prompt-sections/shared.d.ts.map +1 -0
- package/dist/prompt-sections/shared.js +33 -0
- package/dist/prompt-sections/shared.js.map +1 -0
- package/dist/team-prompt-base.d.ts +16 -287
- package/dist/team-prompt-base.d.ts.map +1 -1
- package/dist/team-prompt-base.js +20 -1089
- package/dist/team-prompt-base.js.map +1 -1
- package/dist/templates/claude-md.d.ts.map +1 -1
- package/dist/templates/claude-md.js +0 -1
- package/dist/templates/claude-md.js.map +1 -1
- package/package.json +1 -1
package/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commitSha": "
|
|
3
|
-
"builtAt": "2026-05-
|
|
2
|
+
"commitSha": "9f4d5bf8",
|
|
3
|
+
"builtAt": "2026-05-29T18:59:42.051Z",
|
|
4
4
|
"expectedMigrations": [
|
|
5
5
|
"20250829113330_create_org_nodes_table.sql",
|
|
6
6
|
"20250829113402_fix_function_security_search_path.sql",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directive queue: the pending spawn-directive store + its identity/guard logic.
|
|
3
|
+
*
|
|
4
|
+
* Holds the single shared `pendingSpawnDirectives` map (focusId ->
|
|
5
|
+
* PendingSpawnDirective) that executeSpawnDirective sets and spawnFocusTeam
|
|
6
|
+
* consumes, plus the pure helpers that compute a directive's identity hash and
|
|
7
|
+
* decide spawn-skip / re-fire suppression. Extracted from directive-executor.ts
|
|
8
|
+
* so the queue state and its accessors live in one focused module; the executor
|
|
9
|
+
* imports the map binding + helpers it calls in-line and re-exports the full
|
|
10
|
+
* surface (no duplicate instances).
|
|
11
|
+
*/
|
|
12
|
+
import type { StageDirective } from '../types.js';
|
|
13
|
+
import type { SessionContinuity } from '../session-lineage.js';
|
|
14
|
+
/**
|
|
15
|
+
* Pending spawn directive content, keyed by focusId.
|
|
16
|
+
* Set by executeSpawnDirective, consumed by spawnFocusTeam.
|
|
17
|
+
*/
|
|
18
|
+
export interface PendingSpawnDirective {
|
|
19
|
+
message: string;
|
|
20
|
+
model: string | null;
|
|
21
|
+
sessionType: 'coding' | 'review';
|
|
22
|
+
/** Session-id map slot this spawn reads/writes (INJ-B). */
|
|
23
|
+
lineage: string;
|
|
24
|
+
/** Resume-vs-fresh policy for this spawn's lineage (INJ-B). */
|
|
25
|
+
continuity: SessionContinuity;
|
|
26
|
+
/**
|
|
27
|
+
* SHA-256 hash of the directive's identity ({ prompt, assembly, model }).
|
|
28
|
+
* Recorded on FocusTeamState.lastConsumedDirectiveHash when the team
|
|
29
|
+
* consumes this directive, so the spawn-guard in executeSpawnDirective
|
|
30
|
+
* can detect divergent content within the 60s skip window.
|
|
31
|
+
*/
|
|
32
|
+
contentHash: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Single shared instance of the pending spawn directive store. The executor
|
|
36
|
+
* sets entries directly via this binding; external callers use the accessor
|
|
37
|
+
* functions below.
|
|
38
|
+
*/
|
|
39
|
+
export declare const pendingSpawnDirectives: Map<string, PendingSpawnDirective>;
|
|
40
|
+
/**
|
|
41
|
+
* Compute a stable hash for a stage directive's identity. Hashes only the
|
|
42
|
+
* fields that determine "what the agent is being told to do" -- prompt,
|
|
43
|
+
* assembly recipe, model -- not the assembled output, since that varies
|
|
44
|
+
* with git state and time.
|
|
45
|
+
*/
|
|
46
|
+
export declare function computeDirectiveHash(directive: StageDirective): string;
|
|
47
|
+
/** Window during which a freshly-spawned team is assumed to have consumed
|
|
48
|
+
* the directive that produced it. */
|
|
49
|
+
export declare const SPAWN_GUARD_WINDOW_MS = 60000;
|
|
50
|
+
/**
|
|
51
|
+
* Decide whether executeSpawnDirective should skip the terminate+respawn
|
|
52
|
+
* cycle for a recently-spawned team. Pure function so the divergence rule
|
|
53
|
+
* is unit-testable without mocking the active teams map.
|
|
54
|
+
*
|
|
55
|
+
* - skip: team is within the spawn window AND its last-consumed directive
|
|
56
|
+
* hash matches the new directive's hash. The team is already running
|
|
57
|
+
* under the same intent.
|
|
58
|
+
* - respawn: team is older than the window OR the hashes diverge. Both
|
|
59
|
+
* cases require terminate+respawn so the team picks up fresh intent.
|
|
60
|
+
*/
|
|
61
|
+
export declare function shouldSkipSpawnDirective(args: {
|
|
62
|
+
team: {
|
|
63
|
+
startedAt: Date;
|
|
64
|
+
lastConsumedDirectiveHash: string | null;
|
|
65
|
+
} | undefined;
|
|
66
|
+
contentHash: string;
|
|
67
|
+
now: number;
|
|
68
|
+
}): 'skip' | 'respawn';
|
|
69
|
+
/**
|
|
70
|
+
* Consume a pending spawn directive for a focus.
|
|
71
|
+
* Returns the directive content and removes it from the map.
|
|
72
|
+
* Called by spawnFocusTeam after spawning to send the directive.
|
|
73
|
+
*/
|
|
74
|
+
export declare function consumePendingSpawnDirective(focusId: string): PendingSpawnDirective | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Test-only helper: prime a pending spawn directive without going through
|
|
77
|
+
* executeSpawnDirective. Tests use this to verify consume semantics with
|
|
78
|
+
* a known contentHash.
|
|
79
|
+
*/
|
|
80
|
+
export declare function __setPendingSpawnDirectiveForTesting(focusId: string, directive: PendingSpawnDirective): void;
|
|
81
|
+
/**
|
|
82
|
+
* Check if a focus has a pending spawn directive.
|
|
83
|
+
* Used by the spawn guard to skip actionable-delivery checks.
|
|
84
|
+
*/
|
|
85
|
+
export declare function hasPendingSpawnDirective(focusId: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Non-destructive read of a focus's pending spawn directive (unlike
|
|
88
|
+
* consumePendingSpawnDirective, this does NOT delete it). Used by the
|
|
89
|
+
* stranded-review re-fire bound to compare the queued directive's contentHash
|
|
90
|
+
* against the candidate about to be re-fired.
|
|
91
|
+
*/
|
|
92
|
+
export declare function getPendingSpawnDirective(focusId: string): PendingSpawnDirective | undefined;
|
|
93
|
+
export declare function getPendingSpawnFocusIds(): string[];
|
|
94
|
+
/**
|
|
95
|
+
* Decide whether to SUPPRESS a stranded-review re-fire because an equivalent
|
|
96
|
+
* spawn directive is already queued and unconsumed.
|
|
97
|
+
*
|
|
98
|
+
* The stranded-review recovery re-fires a review directive when the focus is
|
|
99
|
+
* stuck in the review stage with no active team. But executeSpawnDirective
|
|
100
|
+
* re-stores the directive into the pending map on every call, and when there is
|
|
101
|
+
* no team to consume it the directive simply sits there. Without this bound,
|
|
102
|
+
* each poll re-fires, re-stores the same directive, and holds the pending-spawn
|
|
103
|
+
* condition true forever (root cause #18 -- the per-poll spin). When a pending
|
|
104
|
+
* directive with the SAME contentHash already exists, the queued directive will
|
|
105
|
+
* be picked up by the next spawn, so re-firing is a no-op churn: suppress it.
|
|
106
|
+
* A diverged contentHash (an updated stage directive) is NOT suppressed -- the
|
|
107
|
+
* fresh intent must replace the stale queued directive.
|
|
108
|
+
*
|
|
109
|
+
* Pure and exported for unit testing.
|
|
110
|
+
*/
|
|
111
|
+
export declare function shouldSuppressRefireForPendingSpawn(input: {
|
|
112
|
+
pending: {
|
|
113
|
+
contentHash: string;
|
|
114
|
+
} | undefined;
|
|
115
|
+
candidateHash: string;
|
|
116
|
+
}): boolean;
|
|
117
|
+
//# sourceMappingURL=directive-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directive-queue.d.ts","sourceRoot":"","sources":["../../src/directive/directive-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAI/D;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACjC,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,oCAA2C,CAAC;AAE/E;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAMtE;AAED;sCACsC;AACtC,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,IAAI,EAAE;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,GAAG,SAAS,CAMrB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAM/F;AAED;;;;GAIG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,qBAAqB,GAC/B,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAE3F;AAED,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAElD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mCAAmC,CAAC,KAAK,EAAE;IACzD,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAEV"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directive queue: the pending spawn-directive store + its identity/guard logic.
|
|
3
|
+
*
|
|
4
|
+
* Holds the single shared `pendingSpawnDirectives` map (focusId ->
|
|
5
|
+
* PendingSpawnDirective) that executeSpawnDirective sets and spawnFocusTeam
|
|
6
|
+
* consumes, plus the pure helpers that compute a directive's identity hash and
|
|
7
|
+
* decide spawn-skip / re-fire suppression. Extracted from directive-executor.ts
|
|
8
|
+
* so the queue state and its accessors live in one focused module; the executor
|
|
9
|
+
* imports the map binding + helpers it calls in-line and re-exports the full
|
|
10
|
+
* surface (no duplicate instances).
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Single shared instance of the pending spawn directive store. The executor
|
|
15
|
+
* sets entries directly via this binding; external callers use the accessor
|
|
16
|
+
* functions below.
|
|
17
|
+
*/
|
|
18
|
+
export const pendingSpawnDirectives = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Compute a stable hash for a stage directive's identity. Hashes only the
|
|
21
|
+
* fields that determine "what the agent is being told to do" -- prompt,
|
|
22
|
+
* assembly recipe, model -- not the assembled output, since that varies
|
|
23
|
+
* with git state and time.
|
|
24
|
+
*/
|
|
25
|
+
export function computeDirectiveHash(directive) {
|
|
26
|
+
return createHash('sha256').update(JSON.stringify({
|
|
27
|
+
prompt: directive.prompt ?? null,
|
|
28
|
+
assembly: directive.assembly ?? [],
|
|
29
|
+
model: directive.model ?? null,
|
|
30
|
+
})).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
/** Window during which a freshly-spawned team is assumed to have consumed
|
|
33
|
+
* the directive that produced it. */
|
|
34
|
+
export const SPAWN_GUARD_WINDOW_MS = 60000;
|
|
35
|
+
/**
|
|
36
|
+
* Decide whether executeSpawnDirective should skip the terminate+respawn
|
|
37
|
+
* cycle for a recently-spawned team. Pure function so the divergence rule
|
|
38
|
+
* is unit-testable without mocking the active teams map.
|
|
39
|
+
*
|
|
40
|
+
* - skip: team is within the spawn window AND its last-consumed directive
|
|
41
|
+
* hash matches the new directive's hash. The team is already running
|
|
42
|
+
* under the same intent.
|
|
43
|
+
* - respawn: team is older than the window OR the hashes diverge. Both
|
|
44
|
+
* cases require terminate+respawn so the team picks up fresh intent.
|
|
45
|
+
*/
|
|
46
|
+
export function shouldSkipSpawnDirective(args) {
|
|
47
|
+
const { team, contentHash, now } = args;
|
|
48
|
+
if (!team)
|
|
49
|
+
return 'respawn';
|
|
50
|
+
const age = now - team.startedAt.getTime();
|
|
51
|
+
if (age >= SPAWN_GUARD_WINDOW_MS)
|
|
52
|
+
return 'respawn';
|
|
53
|
+
return team.lastConsumedDirectiveHash === contentHash ? 'skip' : 'respawn';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Consume a pending spawn directive for a focus.
|
|
57
|
+
* Returns the directive content and removes it from the map.
|
|
58
|
+
* Called by spawnFocusTeam after spawning to send the directive.
|
|
59
|
+
*/
|
|
60
|
+
export function consumePendingSpawnDirective(focusId) {
|
|
61
|
+
const directive = pendingSpawnDirectives.get(focusId);
|
|
62
|
+
if (directive) {
|
|
63
|
+
pendingSpawnDirectives.delete(focusId);
|
|
64
|
+
}
|
|
65
|
+
return directive;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Test-only helper: prime a pending spawn directive without going through
|
|
69
|
+
* executeSpawnDirective. Tests use this to verify consume semantics with
|
|
70
|
+
* a known contentHash.
|
|
71
|
+
*/
|
|
72
|
+
export function __setPendingSpawnDirectiveForTesting(focusId, directive) {
|
|
73
|
+
pendingSpawnDirectives.set(focusId, directive);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if a focus has a pending spawn directive.
|
|
77
|
+
* Used by the spawn guard to skip actionable-delivery checks.
|
|
78
|
+
*/
|
|
79
|
+
export function hasPendingSpawnDirective(focusId) {
|
|
80
|
+
return pendingSpawnDirectives.has(focusId);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Non-destructive read of a focus's pending spawn directive (unlike
|
|
84
|
+
* consumePendingSpawnDirective, this does NOT delete it). Used by the
|
|
85
|
+
* stranded-review re-fire bound to compare the queued directive's contentHash
|
|
86
|
+
* against the candidate about to be re-fired.
|
|
87
|
+
*/
|
|
88
|
+
export function getPendingSpawnDirective(focusId) {
|
|
89
|
+
return pendingSpawnDirectives.get(focusId);
|
|
90
|
+
}
|
|
91
|
+
export function getPendingSpawnFocusIds() {
|
|
92
|
+
return [...pendingSpawnDirectives.keys()];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Decide whether to SUPPRESS a stranded-review re-fire because an equivalent
|
|
96
|
+
* spawn directive is already queued and unconsumed.
|
|
97
|
+
*
|
|
98
|
+
* The stranded-review recovery re-fires a review directive when the focus is
|
|
99
|
+
* stuck in the review stage with no active team. But executeSpawnDirective
|
|
100
|
+
* re-stores the directive into the pending map on every call, and when there is
|
|
101
|
+
* no team to consume it the directive simply sits there. Without this bound,
|
|
102
|
+
* each poll re-fires, re-stores the same directive, and holds the pending-spawn
|
|
103
|
+
* condition true forever (root cause #18 -- the per-poll spin). When a pending
|
|
104
|
+
* directive with the SAME contentHash already exists, the queued directive will
|
|
105
|
+
* be picked up by the next spawn, so re-firing is a no-op churn: suppress it.
|
|
106
|
+
* A diverged contentHash (an updated stage directive) is NOT suppressed -- the
|
|
107
|
+
* fresh intent must replace the stale queued directive.
|
|
108
|
+
*
|
|
109
|
+
* Pure and exported for unit testing.
|
|
110
|
+
*/
|
|
111
|
+
export function shouldSuppressRefireForPendingSpawn(input) {
|
|
112
|
+
return input.pending !== undefined && input.pending.contentHash === input.candidateHash;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=directive-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directive-queue.js","sourceRoot":"","sources":["../../src/directive/directive-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA2BzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAiC,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAyB;IAC5D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QAChD,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,IAAI;QAChC,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,EAAE;QAClC,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,IAAI;KAC/B,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;sCACsC;AACtC,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAIxC;IACC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC3C,IAAI,GAAG,IAAI,qBAAqB;QAAE,OAAO,SAAS,CAAC;IACnD,OAAO,IAAI,CAAC,yBAAyB,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAe;IAC1D,MAAM,SAAS,GAAG,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,SAAS,EAAE,CAAC;QACd,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oCAAoC,CAClD,OAAe,EACf,SAAgC;IAEhC,sBAAsB,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,OAAO,CAAC,GAAG,sBAAsB,CAAC,IAAI,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mCAAmC,CAAC,KAGnD;IACC,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,KAAK,KAAK,CAAC,aAAa,CAAC;AAC1F,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage tracker: per-focus last-processed workflow stage state.
|
|
3
|
+
*
|
|
4
|
+
* Holds the single shared `lastProcessedStages` map (focusId -> stageId) that
|
|
5
|
+
* the directive edge-trigger uses to fire a stage directive exactly once per
|
|
6
|
+
* stage entry. Extracted from directive-executor.ts so the mutable state and
|
|
7
|
+
* its accessors live in one focused module; directive-executor imports the map
|
|
8
|
+
* binding for its in-loop reads/writes and re-exports the accessors.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Track the last-processed stage ID per focus to prevent
|
|
12
|
+
* double-injection on daemon restart or rapid poll cycles.
|
|
13
|
+
*
|
|
14
|
+
* This is the single shared instance. directive-executor's checkDirectives
|
|
15
|
+
* reads/writes it directly via this binding; external callers use the
|
|
16
|
+
* accessor functions below.
|
|
17
|
+
*/
|
|
18
|
+
export declare const lastProcessedStages: Map<string, string>;
|
|
19
|
+
/** Seed the last-processed stage for a focus (used on daemon startup). */
|
|
20
|
+
export declare function seedLastProcessedStage(focusId: string, stageId: string): void;
|
|
21
|
+
/** Get the last-processed stages map (for persistence). */
|
|
22
|
+
export declare function getLastProcessedStages(): ReadonlyMap<string, string>;
|
|
23
|
+
//# sourceMappingURL=stage-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-tracker.d.ts","sourceRoot":"","sources":["../../src/directive/stage-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,qBAA4B,CAAC;AAE7D,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7E;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAEpE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage tracker: per-focus last-processed workflow stage state.
|
|
3
|
+
*
|
|
4
|
+
* Holds the single shared `lastProcessedStages` map (focusId -> stageId) that
|
|
5
|
+
* the directive edge-trigger uses to fire a stage directive exactly once per
|
|
6
|
+
* stage entry. Extracted from directive-executor.ts so the mutable state and
|
|
7
|
+
* its accessors live in one focused module; directive-executor imports the map
|
|
8
|
+
* binding for its in-loop reads/writes and re-exports the accessors.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Track the last-processed stage ID per focus to prevent
|
|
12
|
+
* double-injection on daemon restart or rapid poll cycles.
|
|
13
|
+
*
|
|
14
|
+
* This is the single shared instance. directive-executor's checkDirectives
|
|
15
|
+
* reads/writes it directly via this binding; external callers use the
|
|
16
|
+
* accessor functions below.
|
|
17
|
+
*/
|
|
18
|
+
export const lastProcessedStages = new Map();
|
|
19
|
+
/** Seed the last-processed stage for a focus (used on daemon startup). */
|
|
20
|
+
export function seedLastProcessedStage(focusId, stageId) {
|
|
21
|
+
lastProcessedStages.set(focusId, stageId);
|
|
22
|
+
}
|
|
23
|
+
/** Get the last-processed stages map (for persistence). */
|
|
24
|
+
export function getLastProcessedStages() {
|
|
25
|
+
return lastProcessedStages;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=stage-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-tracker.js","sourceRoot":"","sources":["../../src/directive/stage-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE7D,0EAA0E;AAC1E,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,OAAe;IACrE,mBAAmB,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,sBAAsB;IACpC,OAAO,mBAAmB,CAAC;AAC7B,CAAC"}
|
|
@@ -9,110 +9,11 @@
|
|
|
9
9
|
import type { DaemonConfig, StageDirective, DirectiveExecutionMode } from './types.js';
|
|
10
10
|
import { type AssemblyManifestEntry } from './assembly-engine.js';
|
|
11
11
|
import './assembly-resolvers.js';
|
|
12
|
-
import { type
|
|
12
|
+
import { type LineageSpec } from './session-lineage.js';
|
|
13
13
|
import { type CloseLoopDecision } from './close-loop-dispatcher.js';
|
|
14
14
|
import { createEscalation } from './queries/issues.js';
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
/** Get the last-processed stages map (for persistence). */
|
|
18
|
-
export declare function getLastProcessedStages(): ReadonlyMap<string, string>;
|
|
19
|
-
/**
|
|
20
|
-
* Pending spawn directive content, keyed by focusId.
|
|
21
|
-
* Set by executeSpawnDirective, consumed by spawnFocusTeam.
|
|
22
|
-
*/
|
|
23
|
-
export interface PendingSpawnDirective {
|
|
24
|
-
message: string;
|
|
25
|
-
model: string | null;
|
|
26
|
-
sessionType: 'coding' | 'review';
|
|
27
|
-
/** Session-id map slot this spawn reads/writes (INJ-B). */
|
|
28
|
-
lineage: string;
|
|
29
|
-
/** Resume-vs-fresh policy for this spawn's lineage (INJ-B). */
|
|
30
|
-
continuity: SessionContinuity;
|
|
31
|
-
/**
|
|
32
|
-
* SHA-256 hash of the directive's identity ({ prompt, assembly, model }).
|
|
33
|
-
* Recorded on FocusTeamState.lastConsumedDirectiveHash when the team
|
|
34
|
-
* consumes this directive, so the spawn-guard in executeSpawnDirective
|
|
35
|
-
* can detect divergent content within the 60s skip window.
|
|
36
|
-
*/
|
|
37
|
-
contentHash: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Compute a stable hash for a stage directive's identity. Hashes only the
|
|
41
|
-
* fields that determine "what the agent is being told to do" -- prompt,
|
|
42
|
-
* assembly recipe, model -- not the assembled output, since that varies
|
|
43
|
-
* with git state and time.
|
|
44
|
-
*/
|
|
45
|
-
export declare function computeDirectiveHash(directive: StageDirective): string;
|
|
46
|
-
/** Window during which a freshly-spawned team is assumed to have consumed
|
|
47
|
-
* the directive that produced it. */
|
|
48
|
-
export declare const SPAWN_GUARD_WINDOW_MS = 60000;
|
|
49
|
-
/**
|
|
50
|
-
* Decide whether executeSpawnDirective should skip the terminate+respawn
|
|
51
|
-
* cycle for a recently-spawned team. Pure function so the divergence rule
|
|
52
|
-
* is unit-testable without mocking the active teams map.
|
|
53
|
-
*
|
|
54
|
-
* - skip: team is within the spawn window AND its last-consumed directive
|
|
55
|
-
* hash matches the new directive's hash. The team is already running
|
|
56
|
-
* under the same intent.
|
|
57
|
-
* - respawn: team is older than the window OR the hashes diverge. Both
|
|
58
|
-
* cases require terminate+respawn so the team picks up fresh intent.
|
|
59
|
-
*/
|
|
60
|
-
export declare function shouldSkipSpawnDirective(args: {
|
|
61
|
-
team: {
|
|
62
|
-
startedAt: Date;
|
|
63
|
-
lastConsumedDirectiveHash: string | null;
|
|
64
|
-
} | undefined;
|
|
65
|
-
contentHash: string;
|
|
66
|
-
now: number;
|
|
67
|
-
}): 'skip' | 'respawn';
|
|
68
|
-
/**
|
|
69
|
-
* Consume a pending spawn directive for a focus.
|
|
70
|
-
* Returns the directive content and removes it from the map.
|
|
71
|
-
* Called by spawnFocusTeam after spawning to send the directive.
|
|
72
|
-
*/
|
|
73
|
-
export declare function consumePendingSpawnDirective(focusId: string): PendingSpawnDirective | undefined;
|
|
74
|
-
/**
|
|
75
|
-
* Test-only helper: prime a pending spawn directive without going through
|
|
76
|
-
* executeSpawnDirective. Tests use this to verify consume semantics with
|
|
77
|
-
* a known contentHash.
|
|
78
|
-
*/
|
|
79
|
-
export declare function __setPendingSpawnDirectiveForTesting(focusId: string, directive: PendingSpawnDirective): void;
|
|
80
|
-
/**
|
|
81
|
-
* Check if a focus has a pending spawn directive.
|
|
82
|
-
* Used by the spawn guard to skip actionable-delivery checks.
|
|
83
|
-
*/
|
|
84
|
-
export declare function hasPendingSpawnDirective(focusId: string): boolean;
|
|
85
|
-
/**
|
|
86
|
-
* Non-destructive read of a focus's pending spawn directive (unlike
|
|
87
|
-
* consumePendingSpawnDirective, this does NOT delete it). Used by the
|
|
88
|
-
* stranded-review re-fire bound to compare the queued directive's contentHash
|
|
89
|
-
* against the candidate about to be re-fired.
|
|
90
|
-
*/
|
|
91
|
-
export declare function getPendingSpawnDirective(focusId: string): PendingSpawnDirective | undefined;
|
|
92
|
-
export declare function getPendingSpawnFocusIds(): string[];
|
|
93
|
-
/**
|
|
94
|
-
* Decide whether to SUPPRESS a stranded-review re-fire because an equivalent
|
|
95
|
-
* spawn directive is already queued and unconsumed.
|
|
96
|
-
*
|
|
97
|
-
* The stranded-review recovery re-fires a review directive when the focus is
|
|
98
|
-
* stuck in the review stage with no active team. But executeSpawnDirective
|
|
99
|
-
* re-stores the directive into the pending map on every call, and when there is
|
|
100
|
-
* no team to consume it the directive simply sits there. Without this bound,
|
|
101
|
-
* each poll re-fires, re-stores the same directive, and holds the pending-spawn
|
|
102
|
-
* condition true forever (root cause #18 -- the per-poll spin). When a pending
|
|
103
|
-
* directive with the SAME contentHash already exists, the queued directive will
|
|
104
|
-
* be picked up by the next spawn, so re-firing is a no-op churn: suppress it.
|
|
105
|
-
* A diverged contentHash (an updated stage directive) is NOT suppressed -- the
|
|
106
|
-
* fresh intent must replace the stale queued directive.
|
|
107
|
-
*
|
|
108
|
-
* Pure and exported for unit testing.
|
|
109
|
-
*/
|
|
110
|
-
export declare function shouldSuppressRefireForPendingSpawn(input: {
|
|
111
|
-
pending: {
|
|
112
|
-
contentHash: string;
|
|
113
|
-
} | undefined;
|
|
114
|
-
candidateHash: string;
|
|
115
|
-
}): boolean;
|
|
15
|
+
export { seedLastProcessedStage, getLastProcessedStages } from './directive/stage-tracker.js';
|
|
16
|
+
export { computeDirectiveHash, SPAWN_GUARD_WINDOW_MS, shouldSkipSpawnDirective, consumePendingSpawnDirective, __setPendingSpawnDirectiveForTesting, hasPendingSpawnDirective, getPendingSpawnDirective, getPendingSpawnFocusIds, shouldSuppressRefireForPendingSpawn, type PendingSpawnDirective, } from './directive/directive-queue.js';
|
|
116
17
|
/** Assembled directive content paired with its per-source attribution manifest. */
|
|
117
18
|
export interface AssembledDirective {
|
|
118
19
|
content: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directive-executor.d.ts","sourceRoot":"","sources":["../src/directive-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"directive-executor.d.ts","sourceRoot":"","sources":["../src/directive-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
* For spawn mode: terminates current session, assembles directive content,
|
|
7
7
|
* and stores it for the next spawn cycle to deliver.
|
|
8
8
|
*/
|
|
9
|
-
import { createHash } from 'node:crypto';
|
|
10
9
|
import { getActiveTeams } from './focus-team-state.js';
|
|
11
10
|
import { resolveAssemblyRecipeWithManifest, } from './assembly-engine.js';
|
|
12
|
-
//
|
|
11
|
+
// Side-effect import: registers assembly resolvers with assembly-engine at module load. Required - do not remove.
|
|
13
12
|
import './assembly-resolvers.js';
|
|
14
13
|
import { resolveLineageSpec } from './session-lineage.js';
|
|
15
14
|
import { fetchFocusWorkflow, fetchFocusWorkflowWithTransitions, getFocusDeliveries, updateFocusStage, updateFocusWorkflowStage, } from './queries/focuses.js';
|
|
@@ -28,116 +27,19 @@ import { executeAdvanceLinkedInjections } from './trigger-executor.js';
|
|
|
28
27
|
import { createEscalation } from './queries/issues.js';
|
|
29
28
|
import { ESCALATION_REASONS } from '@telora/daemon-core';
|
|
30
29
|
import { emitLoopTrigger } from './loop-event-bus.js';
|
|
31
|
-
// ── Stage tracking
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
const pendingSpawnDirectives = new Map();
|
|
46
|
-
/**
|
|
47
|
-
* Compute a stable hash for a stage directive's identity. Hashes only the
|
|
48
|
-
* fields that determine "what the agent is being told to do" -- prompt,
|
|
49
|
-
* assembly recipe, model -- not the assembled output, since that varies
|
|
50
|
-
* with git state and time.
|
|
51
|
-
*/
|
|
52
|
-
export function computeDirectiveHash(directive) {
|
|
53
|
-
return createHash('sha256').update(JSON.stringify({
|
|
54
|
-
prompt: directive.prompt ?? null,
|
|
55
|
-
assembly: directive.assembly ?? [],
|
|
56
|
-
model: directive.model ?? null,
|
|
57
|
-
})).digest('hex');
|
|
58
|
-
}
|
|
59
|
-
/** Window during which a freshly-spawned team is assumed to have consumed
|
|
60
|
-
* the directive that produced it. */
|
|
61
|
-
export const SPAWN_GUARD_WINDOW_MS = 60000;
|
|
62
|
-
/**
|
|
63
|
-
* Decide whether executeSpawnDirective should skip the terminate+respawn
|
|
64
|
-
* cycle for a recently-spawned team. Pure function so the divergence rule
|
|
65
|
-
* is unit-testable without mocking the active teams map.
|
|
66
|
-
*
|
|
67
|
-
* - skip: team is within the spawn window AND its last-consumed directive
|
|
68
|
-
* hash matches the new directive's hash. The team is already running
|
|
69
|
-
* under the same intent.
|
|
70
|
-
* - respawn: team is older than the window OR the hashes diverge. Both
|
|
71
|
-
* cases require terminate+respawn so the team picks up fresh intent.
|
|
72
|
-
*/
|
|
73
|
-
export function shouldSkipSpawnDirective(args) {
|
|
74
|
-
const { team, contentHash, now } = args;
|
|
75
|
-
if (!team)
|
|
76
|
-
return 'respawn';
|
|
77
|
-
const age = now - team.startedAt.getTime();
|
|
78
|
-
if (age >= SPAWN_GUARD_WINDOW_MS)
|
|
79
|
-
return 'respawn';
|
|
80
|
-
return team.lastConsumedDirectiveHash === contentHash ? 'skip' : 'respawn';
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Consume a pending spawn directive for a focus.
|
|
84
|
-
* Returns the directive content and removes it from the map.
|
|
85
|
-
* Called by spawnFocusTeam after spawning to send the directive.
|
|
86
|
-
*/
|
|
87
|
-
export function consumePendingSpawnDirective(focusId) {
|
|
88
|
-
const directive = pendingSpawnDirectives.get(focusId);
|
|
89
|
-
if (directive) {
|
|
90
|
-
pendingSpawnDirectives.delete(focusId);
|
|
91
|
-
}
|
|
92
|
-
return directive;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Test-only helper: prime a pending spawn directive without going through
|
|
96
|
-
* executeSpawnDirective. Tests use this to verify consume semantics with
|
|
97
|
-
* a known contentHash.
|
|
98
|
-
*/
|
|
99
|
-
export function __setPendingSpawnDirectiveForTesting(focusId, directive) {
|
|
100
|
-
pendingSpawnDirectives.set(focusId, directive);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Check if a focus has a pending spawn directive.
|
|
104
|
-
* Used by the spawn guard to skip actionable-delivery checks.
|
|
105
|
-
*/
|
|
106
|
-
export function hasPendingSpawnDirective(focusId) {
|
|
107
|
-
return pendingSpawnDirectives.has(focusId);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Non-destructive read of a focus's pending spawn directive (unlike
|
|
111
|
-
* consumePendingSpawnDirective, this does NOT delete it). Used by the
|
|
112
|
-
* stranded-review re-fire bound to compare the queued directive's contentHash
|
|
113
|
-
* against the candidate about to be re-fired.
|
|
114
|
-
*/
|
|
115
|
-
export function getPendingSpawnDirective(focusId) {
|
|
116
|
-
return pendingSpawnDirectives.get(focusId);
|
|
117
|
-
}
|
|
118
|
-
export function getPendingSpawnFocusIds() {
|
|
119
|
-
return [...pendingSpawnDirectives.keys()];
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Decide whether to SUPPRESS a stranded-review re-fire because an equivalent
|
|
123
|
-
* spawn directive is already queued and unconsumed.
|
|
124
|
-
*
|
|
125
|
-
* The stranded-review recovery re-fires a review directive when the focus is
|
|
126
|
-
* stuck in the review stage with no active team. But executeSpawnDirective
|
|
127
|
-
* re-stores the directive into the pending map on every call, and when there is
|
|
128
|
-
* no team to consume it the directive simply sits there. Without this bound,
|
|
129
|
-
* each poll re-fires, re-stores the same directive, and holds the pending-spawn
|
|
130
|
-
* condition true forever (root cause #18 -- the per-poll spin). When a pending
|
|
131
|
-
* directive with the SAME contentHash already exists, the queued directive will
|
|
132
|
-
* be picked up by the next spawn, so re-firing is a no-op churn: suppress it.
|
|
133
|
-
* A diverged contentHash (an updated stage directive) is NOT suppressed -- the
|
|
134
|
-
* fresh intent must replace the stale queued directive.
|
|
135
|
-
*
|
|
136
|
-
* Pure and exported for unit testing.
|
|
137
|
-
*/
|
|
138
|
-
export function shouldSuppressRefireForPendingSpawn(input) {
|
|
139
|
-
return input.pending !== undefined && input.pending.contentHash === input.candidateHash;
|
|
140
|
-
}
|
|
30
|
+
// ── Stage tracking (extracted to directive/stage-tracker.ts) ─────
|
|
31
|
+
// The lastProcessedStages map + its accessors now live in a focused module.
|
|
32
|
+
// directive-executor imports the map binding for its in-loop reads/writes and
|
|
33
|
+
// RE-EXPORTS the accessors so existing importers are unaffected.
|
|
34
|
+
import { lastProcessedStages } from './directive/stage-tracker.js';
|
|
35
|
+
export { seedLastProcessedStage, getLastProcessedStages } from './directive/stage-tracker.js';
|
|
36
|
+
// ── Pending spawn directives (extracted to directive/directive-queue.ts) ──
|
|
37
|
+
// The pendingSpawnDirectives map + its accessors and the directive identity /
|
|
38
|
+
// spawn-guard helpers now live in a focused module. directive-executor imports
|
|
39
|
+
// the map binding + helpers it calls in-line and RE-EXPORTS the full surface so
|
|
40
|
+
// existing importers (focus-executor, listener, tests) are unaffected.
|
|
41
|
+
import { pendingSpawnDirectives, computeDirectiveHash, SPAWN_GUARD_WINDOW_MS, shouldSkipSpawnDirective, getPendingSpawnDirective, shouldSuppressRefireForPendingSpawn, } from './directive/directive-queue.js';
|
|
42
|
+
export { computeDirectiveHash, SPAWN_GUARD_WINDOW_MS, shouldSkipSpawnDirective, consumePendingSpawnDirective, __setPendingSpawnDirectiveForTesting, hasPendingSpawnDirective, getPendingSpawnDirective, getPendingSpawnFocusIds, shouldSuppressRefireForPendingSpawn, } from './directive/directive-queue.js';
|
|
141
43
|
/**
|
|
142
44
|
* Assemble directive content from a StageDirective's recipe and prompt.
|
|
143
45
|
* Shared by both inject and spawn modes, and by focus-executor for
|