@inixiative/foundry-core 0.5.11 → 0.5.12
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 -1
- package/src/adapters/markdown-hierarchy.ts +30 -2
- package/src/event-stream.ts +42 -1
- package/src/index.ts +7 -1
- package/src/model-registry.ts +37 -3
- package/src/scope.ts +4 -0
- package/src/thread.ts +6 -0
- package/src/token-tracker.ts +3 -2
package/package.json
CHANGED
|
@@ -37,7 +37,7 @@ export interface HierarchyMatch {
|
|
|
37
37
|
readonly owners?: readonly string[];
|
|
38
38
|
/** The harness the composed prompt is for. */
|
|
39
39
|
readonly harness?: readonly string[];
|
|
40
|
-
/** The role composed for (e.g.
|
|
40
|
+
/** The role composed for (e.g. a thread role, a Warden's agent id), or one of its base roles. */
|
|
41
41
|
readonly roles?: readonly string[];
|
|
42
42
|
/** The slot composed (e.g. "instructions", "domainKnowledge"). */
|
|
43
43
|
readonly slots?: readonly string[];
|
|
@@ -124,6 +124,11 @@ export interface HierarchyLocation {
|
|
|
124
124
|
readonly harnessLoads?: readonly string[];
|
|
125
125
|
/** The role composed for; also `{role}` in paths. */
|
|
126
126
|
readonly role?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Roles whose files come first: a source whose path names `{role}` is read once per base role, in
|
|
129
|
+
* order, then for `role` (e.g. a shared base role under each thread role).
|
|
130
|
+
*/
|
|
131
|
+
readonly baseRoles?: readonly string[];
|
|
127
132
|
/** The slot composed; also `{slot}` in paths. */
|
|
128
133
|
readonly slot?: string;
|
|
129
134
|
/**
|
|
@@ -221,7 +226,14 @@ function matches(when: HierarchyMatch | undefined, location: HierarchyLocation):
|
|
|
221
226
|
if (when.projects && !(location.projectId && when.projects.includes(location.projectId)))
|
|
222
227
|
return false;
|
|
223
228
|
if (when.owners && !(location.owner && when.owners.includes(location.owner))) return false;
|
|
224
|
-
|
|
229
|
+
// A base role counts as the role it sits under (a node for `main` serves every thread role).
|
|
230
|
+
if (
|
|
231
|
+
when.roles &&
|
|
232
|
+
![...(location.baseRoles ?? []), ...(location.role ? [location.role] : [])].some((role) =>
|
|
233
|
+
when.roles!.includes(role),
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
return false;
|
|
225
237
|
if (when.slots && !(location.slot && when.slots.includes(location.slot))) return false;
|
|
226
238
|
if (when.paths && !matchedPath(when.paths, location)) return false;
|
|
227
239
|
return true;
|
|
@@ -480,6 +492,22 @@ export async function discoverHierarchy(
|
|
|
480
492
|
) => {
|
|
481
493
|
if (!matches(source.when, location) || (source.id && loadedNatively.has(source.id))) return;
|
|
482
494
|
if (!slotted && !source.when?.slots && !unslottedApplies(entry.node, location)) return;
|
|
495
|
+
const templates = typeof source.path === 'string' ? [source.path] : source.path;
|
|
496
|
+
const roles =
|
|
497
|
+
location.baseRoles?.length && templates.some((path) => path.includes('{role}'))
|
|
498
|
+
? [...new Set([...location.baseRoles, ...(location.role ? [location.role] : [])])]
|
|
499
|
+
: undefined;
|
|
500
|
+
for (const pass of roles ? roles.map((role) => ({ ...vars, role })) : [vars])
|
|
501
|
+
await collect(entry, source, position, base, pass);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const collect = async (
|
|
505
|
+
entry: SelectedHierarchyNode,
|
|
506
|
+
source: HierarchyFileSource,
|
|
507
|
+
position: number,
|
|
508
|
+
base: string | undefined,
|
|
509
|
+
vars: Readonly<Record<string, string | undefined>>,
|
|
510
|
+
) => {
|
|
483
511
|
const place = (path: string) => (isAbsolute(path) ? path : base ? join(base, path) : undefined);
|
|
484
512
|
const literal = (template: string) => {
|
|
485
513
|
const expanded = expandHierarchyPath(template, vars, location.home);
|
package/src/event-stream.ts
CHANGED
|
@@ -42,7 +42,31 @@ export type StreamEvent =
|
|
|
42
42
|
message: string;
|
|
43
43
|
severity: 'error' | 'warn';
|
|
44
44
|
timestamp: number;
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
|
+
| ({ kind: 'activity'; timestamp: number } & SystemActivity);
|
|
47
|
+
|
|
48
|
+
export type ActivityEvent = Extract<StreamEvent, { kind: 'activity' }>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What the system is doing around a thread, not in it (Aron, 2026-11-11): a project's Wardens
|
|
52
|
+
* starting, a decision process restarting, a rate limit. The viewer keeps the latest per `key`
|
|
53
|
+
* and shows it above the composer; `error` events stay for what failed and needs the person.
|
|
54
|
+
*/
|
|
55
|
+
export interface SystemActivity {
|
|
56
|
+
/** One thing that changes over time ("project-wardens:<projectId>"); a later event replaces it. */
|
|
57
|
+
readonly key: string;
|
|
58
|
+
readonly source: string;
|
|
59
|
+
/** The project it concerns; absent when it touches every thread (a login's rate limit). */
|
|
60
|
+
readonly projectId?: string;
|
|
61
|
+
/** `idle` is kept for the log only; the others show, in the status colours. */
|
|
62
|
+
readonly state: 'working' | 'ready' | 'degraded' | 'failed' | 'idle';
|
|
63
|
+
/** Shown until then unless a later event of the same key replaces it (a rate limit's end). */
|
|
64
|
+
readonly until?: number;
|
|
65
|
+
/** One plain sentence for the person. */
|
|
66
|
+
readonly summary: string;
|
|
67
|
+
/** The technical line, for the expanded log. */
|
|
68
|
+
readonly detail: string;
|
|
69
|
+
}
|
|
46
70
|
|
|
47
71
|
/**
|
|
48
72
|
* Unified event stream across all threads and sessions.
|
|
@@ -56,6 +80,8 @@ export type StreamEvent =
|
|
|
56
80
|
export class EventStream {
|
|
57
81
|
private _listeners: Array<(event: StreamEvent) => void> = [];
|
|
58
82
|
private _history: StreamEvent[] = [];
|
|
83
|
+
/** The latest activity of each key, kept past the history bound: a failure stays until it clears. */
|
|
84
|
+
private _activities = new Map<string, ActivityEvent>();
|
|
59
85
|
private _maxHistory: number;
|
|
60
86
|
|
|
61
87
|
constructor(maxHistory: number = 5000) {
|
|
@@ -64,6 +90,10 @@ export class EventStream {
|
|
|
64
90
|
|
|
65
91
|
/** Push an event into the stream. */
|
|
66
92
|
push(event: StreamEvent): void {
|
|
93
|
+
if (event.kind === 'activity') {
|
|
94
|
+
this._activities.delete(event.key);
|
|
95
|
+
this._activities.set(event.key, event);
|
|
96
|
+
}
|
|
67
97
|
this._history.push(event);
|
|
68
98
|
if (this._history.length > this._maxHistory) {
|
|
69
99
|
this._history.shift();
|
|
@@ -117,8 +147,19 @@ export class EventStream {
|
|
|
117
147
|
this.push({ kind: 'error', source, message, severity, timestamp: Date.now() });
|
|
118
148
|
}
|
|
119
149
|
|
|
150
|
+
/** Push what the system is doing (shown above the composer, never as a toast). */
|
|
151
|
+
pushActivity(activity: SystemActivity): void {
|
|
152
|
+
this.push({ kind: 'activity', ...activity, timestamp: Date.now() });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The latest activity of every key, oldest first. */
|
|
156
|
+
activities(): ActivityEvent[] {
|
|
157
|
+
return [...this._activities.values()];
|
|
158
|
+
}
|
|
159
|
+
|
|
120
160
|
/** Clear history. */
|
|
121
161
|
clear(): void {
|
|
122
162
|
this._history = [];
|
|
163
|
+
this._activities.clear();
|
|
123
164
|
}
|
|
124
165
|
}
|
package/src/index.ts
CHANGED
|
@@ -166,7 +166,13 @@ export {
|
|
|
166
166
|
verifyExpertDelivery,
|
|
167
167
|
} from './delivery-evidence';
|
|
168
168
|
// Observability
|
|
169
|
-
export {
|
|
169
|
+
export {
|
|
170
|
+
type ActivityEvent,
|
|
171
|
+
EventStream,
|
|
172
|
+
type SessionEvent,
|
|
173
|
+
type StreamEvent,
|
|
174
|
+
type SystemActivity,
|
|
175
|
+
} from './event-stream';
|
|
170
176
|
export {
|
|
171
177
|
type ExecuteHandler,
|
|
172
178
|
type ExecuteMeta,
|
package/src/model-registry.ts
CHANGED
|
@@ -245,7 +245,7 @@ export interface ModelSweepOption {
|
|
|
245
245
|
label: string;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
export const MODEL_REGISTRY_UPDATED_AT = '2026-
|
|
248
|
+
export const MODEL_REGISTRY_UPDATED_AT = '2026-11-11';
|
|
249
249
|
|
|
250
250
|
/**
|
|
251
251
|
* Decision default: Claude Haiku 5.5, the cheapest current Claude model tagged `judgment`. Held here
|
|
@@ -292,7 +292,7 @@ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
|
|
|
292
292
|
models: [
|
|
293
293
|
{
|
|
294
294
|
id: 'fable',
|
|
295
|
-
label: 'Fable 5.
|
|
295
|
+
label: 'Fable 5.2',
|
|
296
296
|
tier: 'powerful',
|
|
297
297
|
costTier: 'high',
|
|
298
298
|
contextWindow: 1_000_000,
|
|
@@ -301,7 +301,7 @@ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
|
|
|
301
301
|
nativeAlias: true,
|
|
302
302
|
capabilities: POWERFUL_REASONING,
|
|
303
303
|
reasoning: CLAUDE_REASONING,
|
|
304
|
-
notes: 'Claude Code alias for claude-fable-5-
|
|
304
|
+
notes: 'Claude Code alias for claude-fable-5-2.',
|
|
305
305
|
},
|
|
306
306
|
{
|
|
307
307
|
id: 'opus',
|
|
@@ -348,6 +348,17 @@ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
|
|
|
348
348
|
capabilities: POWERFUL_REASONING,
|
|
349
349
|
reasoning: CLAUDE_REASONING,
|
|
350
350
|
},
|
|
351
|
+
{
|
|
352
|
+
id: 'claude-fable-5-2',
|
|
353
|
+
label: 'Fable 5.2',
|
|
354
|
+
tier: 'powerful',
|
|
355
|
+
costTier: 'high',
|
|
356
|
+
contextWindow: 1_000_000,
|
|
357
|
+
maxOutputTokens: 128_000,
|
|
358
|
+
runtimeKind: 'native-harness',
|
|
359
|
+
capabilities: POWERFUL_REASONING,
|
|
360
|
+
reasoning: CLAUDE_REASONING,
|
|
361
|
+
},
|
|
351
362
|
{
|
|
352
363
|
id: 'claude-fable-5-1',
|
|
353
364
|
label: 'Fable 5.1',
|
|
@@ -382,6 +393,18 @@ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
|
|
|
382
393
|
credential: 'api-key',
|
|
383
394
|
enabledByDefault: true,
|
|
384
395
|
models: [
|
|
396
|
+
{
|
|
397
|
+
id: 'claude-fable-5-2',
|
|
398
|
+
label: 'Claude Fable 5.2',
|
|
399
|
+
tier: 'powerful',
|
|
400
|
+
costTier: 'high',
|
|
401
|
+
contextWindow: 1_000_000,
|
|
402
|
+
maxOutputTokens: 128_000,
|
|
403
|
+
runtimeKind: 'api',
|
|
404
|
+
capabilities: POWERFUL_REASONING,
|
|
405
|
+
reasoning: CLAUDE_REASONING,
|
|
406
|
+
notes: 'Thinking is always on; steer with effort.',
|
|
407
|
+
},
|
|
385
408
|
{
|
|
386
409
|
id: 'claude-fable-5-1',
|
|
387
410
|
label: 'Claude Fable 5.1',
|
|
@@ -416,6 +439,17 @@ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
|
|
|
416
439
|
capabilities: POWERFUL_REASONING,
|
|
417
440
|
reasoning: CLAUDE_REASONING,
|
|
418
441
|
},
|
|
442
|
+
{
|
|
443
|
+
id: 'claude-sonnet-5-5',
|
|
444
|
+
label: 'Claude Sonnet 5.5',
|
|
445
|
+
tier: 'standard',
|
|
446
|
+
costTier: 'medium',
|
|
447
|
+
contextWindow: 1_000_000,
|
|
448
|
+
maxOutputTokens: 128_000,
|
|
449
|
+
runtimeKind: 'api',
|
|
450
|
+
capabilities: STANDARD_REASONING,
|
|
451
|
+
reasoning: CLAUDE_REASONING,
|
|
452
|
+
},
|
|
419
453
|
{
|
|
420
454
|
id: 'claude-sonnet-5',
|
|
421
455
|
label: 'Claude Sonnet 5',
|
package/src/scope.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface OwnershipScope {
|
|
|
16
16
|
readonly projectId?: string;
|
|
17
17
|
/** Where the thread's native session runs. Not ownership: sources that read the workspace resolve against it. */
|
|
18
18
|
readonly cwd?: string;
|
|
19
|
+
/** The thread role it composes as; absent: its worker's default. Not ownership. */
|
|
20
|
+
readonly role?: string;
|
|
21
|
+
/** The worker running its main session. Not ownership. */
|
|
22
|
+
readonly worker?: string;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/** Copy a scope, dropping undefined fields so equality checks are stable. */
|
package/src/thread.ts
CHANGED
|
@@ -282,6 +282,9 @@ export interface ThreadMeta {
|
|
|
282
282
|
/** The harness running this thread's main session (`claude-code`, `codex`). Set at creation; never changed silently. */
|
|
283
283
|
worker?: string;
|
|
284
284
|
|
|
285
|
+
/** The thread role its instructions compose as (`coder`, `assistant`, …). Absent: its worker's default role. */
|
|
286
|
+
role?: string;
|
|
287
|
+
|
|
285
288
|
/** When this thread was created. */
|
|
286
289
|
readonly createdAt: number;
|
|
287
290
|
|
|
@@ -388,6 +391,8 @@ export interface ThreadConfig {
|
|
|
388
391
|
projectId?: string;
|
|
389
392
|
/** The harness running this thread's main session. */
|
|
390
393
|
worker?: string;
|
|
394
|
+
/** The thread role its instructions compose as. */
|
|
395
|
+
role?: string;
|
|
391
396
|
/** Optional token tracker — auto-records costs for every dispatch. */
|
|
392
397
|
tokenTracker?: TokenTracker;
|
|
393
398
|
}
|
|
@@ -441,6 +446,7 @@ export class Thread {
|
|
|
441
446
|
...(opts?.forkedFrom ? { forkedFrom: opts.forkedFrom } : {}),
|
|
442
447
|
projectId: opts?.projectId,
|
|
443
448
|
...(opts?.worker ? { worker: opts.worker } : {}),
|
|
449
|
+
...(opts?.role ? { role: opts.role } : {}),
|
|
444
450
|
createdAt: now,
|
|
445
451
|
lastActiveAt: now,
|
|
446
452
|
};
|
package/src/token-tracker.ts
CHANGED
|
@@ -219,18 +219,19 @@ export interface UsageSummary {
|
|
|
219
219
|
|
|
220
220
|
// ---------------------------------------------------------------------------
|
|
221
221
|
// Default cost table. Anthropic: first-party API list prices per 1M tokens, as published at
|
|
222
|
-
// https://platform.claude.com/docs/en/about-claude/pricing (read 2026-11-
|
|
222
|
+
// https://platform.claude.com/docs/en/about-claude/pricing (read 2026-11-11; cache writes are the
|
|
223
223
|
// 5-minute rate, 1.25x input). Haiku 5.5 is its rate up to 100K-token prompts.
|
|
224
224
|
// ---------------------------------------------------------------------------
|
|
225
225
|
|
|
226
226
|
export const DEFAULT_COST_TABLE: CostTable = {
|
|
227
227
|
anthropic: {
|
|
228
|
+
'claude-fable-5-2': { inputPer1M: 8, outputPer1M: 40, cacheReadPer1M: 0.2 },
|
|
228
229
|
'claude-fable-5-1': { inputPer1M: 10, outputPer1M: 50, cacheReadPer1M: 0.25 },
|
|
229
230
|
'claude-fable-5': { inputPer1M: 10, outputPer1M: 50 },
|
|
230
231
|
'claude-opus-5-5': { inputPer1M: 4, outputPer1M: 20, cacheReadPer1M: 0.2 },
|
|
231
232
|
'claude-opus-5': { inputPer1M: 5, outputPer1M: 25 },
|
|
232
233
|
'claude-opus-4-8': { inputPer1M: 5, outputPer1M: 25 },
|
|
233
|
-
'claude-sonnet-5-5': { inputPer1M: 2, outputPer1M: 10, cacheReadPer1M: 0.
|
|
234
|
+
'claude-sonnet-5-5': { inputPer1M: 2, outputPer1M: 10, cacheReadPer1M: 0.1 },
|
|
234
235
|
'claude-sonnet-5': { inputPer1M: 2, outputPer1M: 10 },
|
|
235
236
|
'claude-haiku-5-5': { inputPer1M: 0.1, outputPer1M: 0.5 },
|
|
236
237
|
'claude-haiku-4-5': { inputPer1M: 1, outputPer1M: 5 },
|