@inixiative/foundry-core 0.2.0 → 0.4.0
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/package.json +1 -6
- package/src/action-prompt.ts +47 -28
- package/src/adapters/file-memory.ts +314 -122
- package/src/adapters/http-memory.ts +19 -24
- package/src/adapters/index.ts +4 -4
- package/src/adapters/markdown-docs.ts +15 -15
- package/src/adapters/sqlite-memory.ts +29 -27
- package/src/base-agent.ts +5 -7
- package/src/cache-lifecycle.ts +14 -14
- package/src/capability.ts +76 -83
- package/src/clarifier.ts +46 -0
- package/src/classifier.ts +6 -9
- package/src/context-layer.ts +125 -78
- package/src/context-stack.ts +15 -14
- package/src/credentials.ts +4 -1
- package/src/decider.ts +7 -8
- package/src/delivery-evidence.ts +206 -55
- package/src/event-stream.ts +34 -20
- package/src/executor.ts +81 -43
- package/src/harness.ts +225 -126
- package/src/hooks.ts +26 -26
- package/src/hydrator.ts +6 -8
- package/src/id.ts +3 -3
- package/src/index.ts +252 -218
- package/src/intervention.ts +21 -12
- package/src/messages.ts +73 -62
- package/src/middleware.ts +9 -19
- package/src/model-registry.ts +1645 -0
- package/src/native-evidence.ts +124 -30
- package/src/permission-middleware.ts +16 -28
- package/src/retry.ts +6 -6
- package/src/router.ts +5 -5
- package/src/signal.ts +16 -11
- package/src/thread.ts +138 -61
- package/src/token-counts.ts +9 -1
- package/src/token-tracker.ts +43 -34
- package/src/tools.ts +138 -95
- package/src/trace.ts +44 -23
- package/src/types.ts +33 -24
package/src/context-layer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OwnershipScope } from
|
|
1
|
+
import type { OwnershipScope } from './scope';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A source that can provide content to a layer.
|
|
@@ -10,7 +10,7 @@ import type { OwnershipScope } from "./scope";
|
|
|
10
10
|
* record) cannot be carried and no retrieval exists. The executor refuses
|
|
11
11
|
* provider execution while any included layer reports it.
|
|
12
12
|
*/
|
|
13
|
-
export const REQUIRED_CONTEXT_BLOCKED =
|
|
13
|
+
export const REQUIRED_CONTEXT_BLOCKED = 'required-context-blocked';
|
|
14
14
|
|
|
15
15
|
/** Logical request identity, independent of dispatches and native sessions. */
|
|
16
16
|
export interface LogicalMessageIdentity extends OwnershipScope {
|
|
@@ -19,15 +19,25 @@ export interface LogicalMessageIdentity extends OwnershipScope {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/** Own only the identity fields; callers cannot change a pending load by reference. */
|
|
22
|
-
export function copyMessageIdentity(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
export function copyMessageIdentity(
|
|
23
|
+
identity: LogicalMessageIdentity | undefined,
|
|
24
|
+
): LogicalMessageIdentity | undefined {
|
|
25
|
+
return identity?.messageId && identity.threadId
|
|
26
|
+
? Object.freeze({
|
|
27
|
+
messageId: identity.messageId,
|
|
28
|
+
threadId: identity.threadId,
|
|
29
|
+
...(identity.projectId ? { projectId: identity.projectId } : {}),
|
|
30
|
+
})
|
|
31
|
+
: undefined;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
function sameMessage(
|
|
30
|
-
|
|
34
|
+
function sameMessage(
|
|
35
|
+
a: LogicalMessageIdentity | undefined,
|
|
36
|
+
b: LogicalMessageIdentity | undefined,
|
|
37
|
+
): boolean {
|
|
38
|
+
return (
|
|
39
|
+
a?.messageId === b?.messageId && a?.threadId === b?.threadId && a?.projectId === b?.projectId
|
|
40
|
+
);
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
export interface SourceLoadHint {
|
|
@@ -49,17 +59,27 @@ export interface SourceSelectionReport {
|
|
|
49
59
|
readonly chars: number;
|
|
50
60
|
readonly kind?: string;
|
|
51
61
|
readonly timestamp?: number;
|
|
52
|
-
readonly truncated?: boolean;
|
|
62
|
+
readonly truncated?: boolean;
|
|
63
|
+
ranges?: Array<readonly [number, number]>;
|
|
53
64
|
readonly matched?: string[];
|
|
54
65
|
}>;
|
|
55
|
-
readonly omitted: Array<{
|
|
56
|
-
readonly
|
|
66
|
+
readonly omitted: Array<{
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly reason: string;
|
|
69
|
+
readonly chars: number;
|
|
70
|
+
readonly kind?: string;
|
|
71
|
+
readonly excludedFor?: LogicalMessageIdentity;
|
|
72
|
+
}>;
|
|
57
73
|
/** Records the reader was allowed to see. */
|
|
58
74
|
readonly considered: number;
|
|
59
75
|
/** Everything retained in the source for this reader, injected or not. */
|
|
60
76
|
readonly retained: { readonly count: number; readonly chars: number };
|
|
61
77
|
readonly budget: { readonly chars: number; readonly used: number; readonly exceeded: boolean };
|
|
62
|
-
readonly conflicts: Array<{
|
|
78
|
+
readonly conflicts: Array<{
|
|
79
|
+
readonly kind: string;
|
|
80
|
+
readonly ids: string[];
|
|
81
|
+
readonly detail: string;
|
|
82
|
+
}>;
|
|
63
83
|
readonly focus?: { readonly hash: string; readonly terms: number };
|
|
64
84
|
readonly currentMessage?: LogicalMessageIdentity;
|
|
65
85
|
}
|
|
@@ -100,10 +120,13 @@ export interface LayerSelectionState {
|
|
|
100
120
|
* layer predates this metadata or was never classified; consumers then fall back
|
|
101
121
|
* to their legacy id heuristic and must not present that as verified provenance.
|
|
102
122
|
*/
|
|
103
|
-
export type LayerSegment =
|
|
104
|
-
export const LAYER_SEGMENTS: readonly LayerSegment[] = Object.freeze([
|
|
123
|
+
export type LayerSegment = 'domain-knowledge' | 'thread-knowledge';
|
|
124
|
+
export const LAYER_SEGMENTS: readonly LayerSegment[] = Object.freeze([
|
|
125
|
+
'domain-knowledge',
|
|
126
|
+
'thread-knowledge',
|
|
127
|
+
]);
|
|
105
128
|
export function isLayerSegment(value: unknown): value is LayerSegment {
|
|
106
|
-
return typeof value ===
|
|
129
|
+
return typeof value === 'string' && (LAYER_SEGMENTS as readonly string[]).includes(value);
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
/**
|
|
@@ -127,7 +150,7 @@ export interface LayerDefinition {
|
|
|
127
150
|
/** Instruction explaining how this layer's content should be used. */
|
|
128
151
|
prompt?: string;
|
|
129
152
|
/** When this layer is activated: always, on-demand, or conditionally. */
|
|
130
|
-
activation?:
|
|
153
|
+
activation?: 'always' | 'on-demand' | 'conditional';
|
|
131
154
|
/** Who can write to instances of this layer. Undefined = anyone. */
|
|
132
155
|
writers?: string[];
|
|
133
156
|
}
|
|
@@ -221,7 +244,7 @@ export interface VersionLog<T = unknown> {
|
|
|
221
244
|
/** What is being versioned: "layer:conventions", "agent:librarian", etc. */
|
|
222
245
|
subject: string;
|
|
223
246
|
/** "definition" or "instance". */
|
|
224
|
-
scope:
|
|
247
|
+
scope: 'definition' | 'instance';
|
|
225
248
|
/** Ordered version entries, newest last. */
|
|
226
249
|
entries: VersionEntry<T>[];
|
|
227
250
|
}
|
|
@@ -244,7 +267,7 @@ export interface ContextLayerConfig {
|
|
|
244
267
|
segment?: LayerSegment;
|
|
245
268
|
}
|
|
246
269
|
|
|
247
|
-
export type LayerState =
|
|
270
|
+
export type LayerState = 'cold' | 'warming' | 'warm' | 'stale' | 'compressing';
|
|
248
271
|
|
|
249
272
|
/** Emitted on any write to a layer instance. The Session Store captures these. */
|
|
250
273
|
export interface LayerMutationEvent {
|
|
@@ -278,9 +301,9 @@ export class ContextLayer {
|
|
|
278
301
|
*/
|
|
279
302
|
readonly segment: LayerSegment | undefined;
|
|
280
303
|
|
|
281
|
-
private _content: string =
|
|
282
|
-
private _state: LayerState =
|
|
283
|
-
private _hash: string =
|
|
304
|
+
private _content: string = '';
|
|
305
|
+
private _state: LayerState = 'cold';
|
|
306
|
+
private _hash: string = '';
|
|
284
307
|
private _lastWarmed: number | null = null;
|
|
285
308
|
private _lastAccessed: number | null = null;
|
|
286
309
|
private _sources: ContextSource[];
|
|
@@ -293,8 +316,7 @@ export class ContextLayer {
|
|
|
293
316
|
private _version?: LayerVersionMark;
|
|
294
317
|
private _warmingPromise: Promise<void> | null = null;
|
|
295
318
|
|
|
296
|
-
private _listeners: Array<(state: LayerState, layer: ContextLayer) => void> =
|
|
297
|
-
[];
|
|
319
|
+
private _listeners: Array<(state: LayerState, layer: ContextLayer) => void> = [];
|
|
298
320
|
|
|
299
321
|
/**
|
|
300
322
|
* Mutation listeners — called on any write to this layer (content, state).
|
|
@@ -302,9 +324,7 @@ export class ContextLayer {
|
|
|
302
324
|
* Unlike onStateChange (which only fires on state transitions), this fires
|
|
303
325
|
* on content changes and any other mutation.
|
|
304
326
|
*/
|
|
305
|
-
private _mutationListeners: Array<
|
|
306
|
-
(event: LayerMutationEvent) => void
|
|
307
|
-
> = [];
|
|
327
|
+
private _mutationListeners: Array<(event: LayerMutationEvent) => void> = [];
|
|
308
328
|
|
|
309
329
|
constructor(config: ContextLayerConfig) {
|
|
310
330
|
this.id = config.id;
|
|
@@ -315,7 +335,8 @@ export class ContextLayer {
|
|
|
315
335
|
this._maxTokens = config.maxTokens ?? config.definition?.maxTokens;
|
|
316
336
|
this._prompt = config.prompt ?? config.definition?.prompt;
|
|
317
337
|
const segment = config.segment ?? config.definition?.segment;
|
|
318
|
-
if (segment !== undefined && !isLayerSegment(segment))
|
|
338
|
+
if (segment !== undefined && !isLayerSegment(segment))
|
|
339
|
+
throw new Error(`Unknown layer segment "${String(segment)}" for layer "${config.id}"`);
|
|
319
340
|
this.segment = segment;
|
|
320
341
|
}
|
|
321
342
|
|
|
@@ -379,7 +400,7 @@ export class ContextLayer {
|
|
|
379
400
|
});
|
|
380
401
|
copy._focus = this._focus;
|
|
381
402
|
copy._currentMessage = this._currentMessage;
|
|
382
|
-
if (this._state !==
|
|
403
|
+
if (this._state !== 'cold') {
|
|
383
404
|
copy.restoreInstance(this.snapshotInstance());
|
|
384
405
|
}
|
|
385
406
|
return copy;
|
|
@@ -393,21 +414,29 @@ export class ContextLayer {
|
|
|
393
414
|
this._lastAccessed = snapshot.lastAccessed;
|
|
394
415
|
this._selection = snapshot.selection ? structuredClone(snapshot.selection) : undefined;
|
|
395
416
|
this._version = undefined;
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
417
|
+
if (
|
|
418
|
+
snapshot.definitionId === (this.definition?.id ?? this.id) &&
|
|
419
|
+
(!this.owner ||
|
|
420
|
+
(snapshot.threadId === this.owner.threadId &&
|
|
421
|
+
snapshot.projectId === this.owner.projectId)) &&
|
|
422
|
+
snapshot.version &&
|
|
423
|
+
this._validVersion(snapshot.version)
|
|
424
|
+
)
|
|
425
|
+
this._version = Object.freeze({ ...snapshot.version });
|
|
426
|
+
const state = snapshot.state === 'warming' ? 'warm' : snapshot.state;
|
|
400
427
|
// Restored content carries the focus it was selected for. If this layer is now focused
|
|
401
428
|
// elsewhere, the content is real but answers another message: stale, not warm.
|
|
402
|
-
this._setState(state ===
|
|
429
|
+
this._setState(state === 'warm' && this._focusMismatch() ? 'stale' : state);
|
|
403
430
|
}
|
|
404
431
|
|
|
405
432
|
/** True when a focusable source's recorded selection focus differs from the current focus. */
|
|
406
433
|
private _focusMismatch(): boolean {
|
|
407
434
|
if (!this._sources.some((source) => source.focusable)) return false;
|
|
408
435
|
const current = this._focus ? computeHash(this._focus) : undefined;
|
|
409
|
-
return (
|
|
410
|
-
|
|
436
|
+
return (
|
|
437
|
+
(this._selection?.focusHash ?? undefined) !== current ||
|
|
438
|
+
!sameMessage(this._selection?.currentMessage, this._currentMessage)
|
|
439
|
+
);
|
|
411
440
|
}
|
|
412
441
|
|
|
413
442
|
// -- Focus --
|
|
@@ -430,8 +459,8 @@ export class ContextLayer {
|
|
|
430
459
|
// Omitting identity deliberately clears it: direct/background work must
|
|
431
460
|
// never inherit the preceding request just because its text is identical.
|
|
432
461
|
this._currentMessage = identity;
|
|
433
|
-
if (this._sources.some((source) => source.focusable) && this._state ===
|
|
434
|
-
this._setState(
|
|
462
|
+
if (this._sources.some((source) => source.focusable) && this._state === 'warm') {
|
|
463
|
+
this._setState('stale');
|
|
435
464
|
}
|
|
436
465
|
}
|
|
437
466
|
|
|
@@ -459,12 +488,12 @@ export class ContextLayer {
|
|
|
459
488
|
/** Check if this layer has become stale. Triggers state transition if so. */
|
|
460
489
|
checkStaleness(): LayerState {
|
|
461
490
|
if (
|
|
462
|
-
this._state ===
|
|
491
|
+
this._state === 'warm' &&
|
|
463
492
|
this._staleness !== undefined &&
|
|
464
493
|
this._lastWarmed !== null &&
|
|
465
494
|
Date.now() - this._lastWarmed > this._staleness
|
|
466
495
|
) {
|
|
467
|
-
this._setState(
|
|
496
|
+
this._setState('stale');
|
|
468
497
|
}
|
|
469
498
|
return this._state;
|
|
470
499
|
}
|
|
@@ -478,12 +507,12 @@ export class ContextLayer {
|
|
|
478
507
|
}
|
|
479
508
|
|
|
480
509
|
get isWarm(): boolean {
|
|
481
|
-
return this._state ===
|
|
510
|
+
return this._state === 'warm';
|
|
482
511
|
}
|
|
483
512
|
|
|
484
513
|
get isStale(): boolean {
|
|
485
514
|
this.checkStaleness();
|
|
486
|
-
return this._state ===
|
|
515
|
+
return this._state === 'stale';
|
|
487
516
|
}
|
|
488
517
|
|
|
489
518
|
// -- Lifecycle --
|
|
@@ -502,7 +531,7 @@ export class ContextLayer {
|
|
|
502
531
|
|
|
503
532
|
private async _doWarm(): Promise<void> {
|
|
504
533
|
const previousState = this._state;
|
|
505
|
-
this._setState(
|
|
534
|
+
this._setState('warming');
|
|
506
535
|
|
|
507
536
|
try {
|
|
508
537
|
// A load is labelled with the focus it was asked for. If a focusable source's focus moves
|
|
@@ -512,69 +541,94 @@ export class ContextLayer {
|
|
|
512
541
|
for (let attempt = 0; ; attempt++) {
|
|
513
542
|
const focus = this._focus;
|
|
514
543
|
const currentMessage = this._currentMessage;
|
|
515
|
-
const hint: SourceLoadHint | undefined =
|
|
516
|
-
|
|
544
|
+
const hint: SourceLoadHint | undefined =
|
|
545
|
+
focus || currentMessage
|
|
546
|
+
? Object.freeze({
|
|
547
|
+
...(focus ? { focus } : {}),
|
|
548
|
+
...(currentMessage ? { currentMessage } : {}),
|
|
549
|
+
})
|
|
550
|
+
: undefined;
|
|
517
551
|
const parts: string[] = [];
|
|
518
|
-
const reports: LayerSelectionState[
|
|
552
|
+
const reports: LayerSelectionState['sources'] = [];
|
|
519
553
|
for (const source of this._sources) {
|
|
520
554
|
parts.push(await source.load(hint));
|
|
521
555
|
const report = source.report?.();
|
|
522
556
|
if (report) reports.push({ sourceId: source.id, report: structuredClone(report) });
|
|
523
557
|
}
|
|
524
558
|
|
|
525
|
-
this._content = parts.join(
|
|
559
|
+
this._content = parts.join('\n\n');
|
|
526
560
|
this._hash = computeHash(this._content);
|
|
527
561
|
this._version = undefined; // even identical unversioned source bytes are a new write
|
|
528
562
|
this._selection = reports.length
|
|
529
|
-
? {
|
|
563
|
+
? {
|
|
564
|
+
...(focus ? { focusHash: computeHash(focus) } : {}),
|
|
565
|
+
...(currentMessage ? { currentMessage } : {}),
|
|
566
|
+
sources: reports,
|
|
567
|
+
}
|
|
530
568
|
: undefined;
|
|
531
569
|
this._lastWarmed = Date.now();
|
|
532
570
|
|
|
533
|
-
const focusMoved =
|
|
571
|
+
const focusMoved =
|
|
572
|
+
(this._focus !== focus || !sameMessage(this._currentMessage, currentMessage)) &&
|
|
534
573
|
this._sources.some((source) => source.focusable);
|
|
535
|
-
if (!focusMoved) {
|
|
536
|
-
|
|
574
|
+
if (!focusMoved) {
|
|
575
|
+
this._setState('warm');
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (attempt >= 2) {
|
|
579
|
+
this._setState('stale');
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
537
582
|
}
|
|
538
583
|
} catch (err) {
|
|
539
584
|
// Revert to previous state so the layer isn't stuck in "warming". Content and selection
|
|
540
585
|
// provenance are whatever the last successful load left; a stale label stays stale.
|
|
541
|
-
const reverted = previousState ===
|
|
586
|
+
const reverted = previousState === 'warming' ? 'cold' : previousState;
|
|
542
587
|
// Content from the last good load stays, but it answers the focus it was
|
|
543
588
|
// selected for. If the focus has moved since, warm would be a lie.
|
|
544
|
-
this._setState(reverted ===
|
|
589
|
+
this._setState(reverted === 'warm' && this._focusMismatch() ? 'stale' : reverted);
|
|
545
590
|
throw err;
|
|
546
591
|
}
|
|
547
592
|
}
|
|
548
593
|
|
|
549
|
-
set(content: string, author: string =
|
|
594
|
+
set(content: string, author: string = 'system'): void {
|
|
550
595
|
const previous = this._content;
|
|
551
596
|
this._content = content;
|
|
552
597
|
this._hash = computeHash(content);
|
|
553
598
|
this._selection = undefined; // manual content has no selection provenance
|
|
554
599
|
this._version = undefined; // a new write carries no version until its owner marks it
|
|
555
600
|
this._lastWarmed = Date.now();
|
|
556
|
-
this._setState(
|
|
601
|
+
this._setState('warm');
|
|
557
602
|
if (previous !== content) {
|
|
558
|
-
this._emitMutation(
|
|
603
|
+
this._emitMutation('content', previous, content, author);
|
|
559
604
|
}
|
|
560
605
|
}
|
|
561
606
|
|
|
562
607
|
invalidate(): void {
|
|
563
|
-
if (this._state !==
|
|
564
|
-
this._setState(
|
|
608
|
+
if (this._state !== 'cold') {
|
|
609
|
+
this._setState('stale');
|
|
565
610
|
}
|
|
566
611
|
}
|
|
567
612
|
|
|
568
613
|
private _validVersion(mark: LayerVersionMark): boolean {
|
|
569
|
-
return
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
614
|
+
return (
|
|
615
|
+
Number.isSafeInteger(mark.revision) &&
|
|
616
|
+
mark.revision >= 0 &&
|
|
617
|
+
mark.hash === this._hash &&
|
|
618
|
+
mark.hash === computeHash(this._content) &&
|
|
619
|
+
(mark.domain === undefined || (typeof mark.domain === 'string' && mark.domain.length > 0)) &&
|
|
620
|
+
[mark.threadId, mark.projectId, mark.author].every(
|
|
621
|
+
(value) => value === undefined || (typeof value === 'string' && value.length > 0),
|
|
622
|
+
) &&
|
|
623
|
+
(!this.owner ||
|
|
624
|
+
(mark.threadId === this.owner.threadId && mark.projectId === this.owner.projectId))
|
|
625
|
+
);
|
|
573
626
|
}
|
|
574
627
|
|
|
575
628
|
/** A rejected mark never replaces the current valid owner mark. */
|
|
576
629
|
markVersion(mark: LayerVersionMark): void {
|
|
577
|
-
if (!mark || !this._validVersion(mark))
|
|
630
|
+
if (!mark || !this._validVersion(mark))
|
|
631
|
+
throw new Error('Layer version mark does not describe the current content and owner');
|
|
578
632
|
this._version = Object.freeze({ ...mark });
|
|
579
633
|
}
|
|
580
634
|
|
|
@@ -583,12 +637,12 @@ export class ContextLayer {
|
|
|
583
637
|
}
|
|
584
638
|
|
|
585
639
|
clear(): void {
|
|
586
|
-
this._content =
|
|
587
|
-
this._hash =
|
|
640
|
+
this._content = '';
|
|
641
|
+
this._hash = '';
|
|
588
642
|
this._lastWarmed = null;
|
|
589
643
|
this._selection = undefined;
|
|
590
644
|
this._version = undefined;
|
|
591
|
-
this._setState(
|
|
645
|
+
this._setState('cold');
|
|
592
646
|
}
|
|
593
647
|
|
|
594
648
|
addSource(source: ContextSource): void {
|
|
@@ -604,9 +658,7 @@ export class ContextLayer {
|
|
|
604
658
|
|
|
605
659
|
// -- Observation --
|
|
606
660
|
|
|
607
|
-
onStateChange(
|
|
608
|
-
listener: (state: LayerState, layer: ContextLayer) => void
|
|
609
|
-
): () => void {
|
|
661
|
+
onStateChange(listener: (state: LayerState, layer: ContextLayer) => void): () => void {
|
|
610
662
|
this._listeners.push(listener);
|
|
611
663
|
return () => {
|
|
612
664
|
const idx = this._listeners.indexOf(listener);
|
|
@@ -624,7 +676,7 @@ export class ContextLayer {
|
|
|
624
676
|
const previous = this._staleness;
|
|
625
677
|
this._staleness = value;
|
|
626
678
|
if (previous !== value) {
|
|
627
|
-
this._emitMutation(
|
|
679
|
+
this._emitMutation('staleness', previous, value, 'system');
|
|
628
680
|
}
|
|
629
681
|
}
|
|
630
682
|
|
|
@@ -644,7 +696,7 @@ export class ContextLayer {
|
|
|
644
696
|
const previous = this._prompt;
|
|
645
697
|
this._prompt = value;
|
|
646
698
|
if (previous !== value) {
|
|
647
|
-
this._emitMutation(
|
|
699
|
+
this._emitMutation('prompt', previous, value, 'system');
|
|
648
700
|
}
|
|
649
701
|
}
|
|
650
702
|
|
|
@@ -670,12 +722,7 @@ export class ContextLayer {
|
|
|
670
722
|
|
|
671
723
|
// -- Internal --
|
|
672
724
|
|
|
673
|
-
private _emitMutation(
|
|
674
|
-
field: string,
|
|
675
|
-
previous: unknown,
|
|
676
|
-
current: unknown,
|
|
677
|
-
author: string,
|
|
678
|
-
): void {
|
|
725
|
+
private _emitMutation(field: string, previous: unknown, current: unknown, author: string): void {
|
|
679
726
|
if (this._mutationListeners.length === 0) return;
|
|
680
727
|
const event: LayerMutationEvent = {
|
|
681
728
|
layerId: this.id,
|
|
@@ -701,6 +748,6 @@ export class ContextLayer {
|
|
|
701
748
|
listener(state, this);
|
|
702
749
|
}
|
|
703
750
|
// Also emit as mutation for instance history
|
|
704
|
-
this._emitMutation(
|
|
751
|
+
this._emitMutation('state', previous, state, 'system');
|
|
705
752
|
}
|
|
706
753
|
}
|
package/src/context-stack.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ContextLayer, computeHash, type LayerState } from
|
|
2
|
-
import type { OwnershipScope } from
|
|
1
|
+
import { type ContextLayer, computeHash, type LayerState } from './context-layer';
|
|
2
|
+
import type { OwnershipScope } from './scope';
|
|
3
3
|
|
|
4
4
|
export type LayerFilter = (layer: ContextLayer) => boolean;
|
|
5
5
|
|
|
@@ -25,7 +25,7 @@ export interface ContextStackView {
|
|
|
25
25
|
|
|
26
26
|
/** A single block in the assembled prompt output. */
|
|
27
27
|
export interface PromptBlock {
|
|
28
|
-
readonly role:
|
|
28
|
+
readonly role: 'system' | 'layer' | 'content';
|
|
29
29
|
readonly id?: string;
|
|
30
30
|
readonly text: string;
|
|
31
31
|
/**
|
|
@@ -128,16 +128,12 @@ export class ContextStack {
|
|
|
128
128
|
async warmAll(): Promise<void> {
|
|
129
129
|
// Check staleness before deciding what to warm
|
|
130
130
|
for (const l of this._layers) l.checkStaleness();
|
|
131
|
-
await Promise.all(
|
|
132
|
-
this._layers.filter((l) => !l.isWarm).map((l) => l.warm())
|
|
133
|
-
);
|
|
131
|
+
await Promise.all(this._layers.filter((l) => !l.isWarm).map((l) => l.warm()));
|
|
134
132
|
}
|
|
135
133
|
|
|
136
134
|
async refresh(): Promise<void> {
|
|
137
135
|
for (const l of this._layers) l.checkStaleness();
|
|
138
|
-
await Promise.all(
|
|
139
|
-
this._layers.filter((l) => l.isStale).map((l) => l.warm())
|
|
140
|
-
);
|
|
136
|
+
await Promise.all(this._layers.filter((l) => l.isStale).map((l) => l.warm()));
|
|
141
137
|
}
|
|
142
138
|
|
|
143
139
|
// -- Merging --
|
|
@@ -147,7 +143,7 @@ export class ContextStack {
|
|
|
147
143
|
return layers
|
|
148
144
|
.filter((l) => l.isWarm && l.content.length > 0)
|
|
149
145
|
.map((l) => l.content)
|
|
150
|
-
.join(
|
|
146
|
+
.join('\n\n');
|
|
151
147
|
}
|
|
152
148
|
|
|
153
149
|
slice(filter: LayerFilter): string {
|
|
@@ -169,7 +165,7 @@ export class ContextStack {
|
|
|
169
165
|
const blocks: PromptBlock[] = [];
|
|
170
166
|
|
|
171
167
|
if (agentPrompt) {
|
|
172
|
-
blocks.push({ role:
|
|
168
|
+
blocks.push({ role: 'system', text: agentPrompt });
|
|
173
169
|
}
|
|
174
170
|
|
|
175
171
|
const layers = filter ? this._layers.filter(filter) : this._layers;
|
|
@@ -179,14 +175,19 @@ export class ContextStack {
|
|
|
179
175
|
|
|
180
176
|
if (layer.prompt) {
|
|
181
177
|
// A layer prompt is always an instruction; it never inherits the content segment.
|
|
182
|
-
blocks.push({ role:
|
|
178
|
+
blocks.push({ role: 'layer', id: layer.id, text: layer.prompt });
|
|
183
179
|
}
|
|
184
180
|
// The content block carries the layer's declared segment when it has one; legacy
|
|
185
181
|
// layers leave it unset and the artifact builder falls back to its id heuristic.
|
|
186
|
-
blocks.push({
|
|
182
|
+
blocks.push({
|
|
183
|
+
role: 'content',
|
|
184
|
+
id: layer.id,
|
|
185
|
+
text: layer.content,
|
|
186
|
+
...(layer.segment ? { segment: layer.segment } : {}),
|
|
187
|
+
});
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
const text = blocks.map((b) => b.text).join(
|
|
190
|
+
const text = blocks.map((b) => b.text).join('\n\n');
|
|
190
191
|
return { blocks, text };
|
|
191
192
|
}
|
|
192
193
|
|
package/src/credentials.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Credential references belong in configuration; secrets stay behind the resolver. */
|
|
2
|
-
|
|
2
|
+
/** `kingdom-signet` names the Signet a paired Kingdom granted, by the destination's origin plus the owner it was paired as. */
|
|
3
|
+
export type CredentialReference =
|
|
4
|
+
| { type: 'managed'; id: string }
|
|
5
|
+
| { type: 'kingdom-signet'; owner: string };
|
|
3
6
|
|
|
4
7
|
export interface CredentialScope {
|
|
5
8
|
service: string;
|
package/src/decider.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { ContextStack, LayerFilter } from
|
|
4
|
-
import type { ExecuteMeta } from
|
|
1
|
+
import { type AgentConfig, BaseAgent, type ExecutionResult } from './base-agent';
|
|
2
|
+
import { computeHash } from './context-layer';
|
|
3
|
+
import type { ContextStack, LayerFilter } from './context-stack';
|
|
4
|
+
import type { ExecuteMeta } from './executor';
|
|
5
5
|
|
|
6
6
|
export interface Decision<T = unknown> {
|
|
7
7
|
readonly value: T;
|
|
@@ -17,11 +17,10 @@ export interface Decision<T = unknown> {
|
|
|
17
17
|
export type DecideHandler<TPayload, TDecision> = (
|
|
18
18
|
context: string,
|
|
19
19
|
payload: TPayload,
|
|
20
|
-
meta?: ExecuteMeta
|
|
20
|
+
meta?: ExecuteMeta,
|
|
21
21
|
) => Promise<Decision<TDecision>>;
|
|
22
22
|
|
|
23
|
-
export interface DeciderConfig<TPayload = unknown, TDecision = unknown>
|
|
24
|
-
extends AgentConfig {
|
|
23
|
+
export interface DeciderConfig<TPayload = unknown, TDecision = unknown> extends AgentConfig {
|
|
25
24
|
handler: DecideHandler<TPayload, TDecision>;
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -51,7 +50,7 @@ export class Decider<TPayload = unknown, TDecision = unknown> extends BaseAgent<
|
|
|
51
50
|
async run(
|
|
52
51
|
payload: TPayload,
|
|
53
52
|
filterOverride?: LayerFilter,
|
|
54
|
-
meta?: ExecuteMeta
|
|
53
|
+
meta?: ExecuteMeta,
|
|
55
54
|
): Promise<ExecutionResult<Decision<TDecision>>> {
|
|
56
55
|
const context = this.getContextWith(filterOverride);
|
|
57
56
|
const contextHash = computeHash(context);
|