@inixiative/foundry-core 0.5.11 → 0.5.13
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 +3 -3
- package/src/adapters/markdown-hierarchy.ts +30 -2
- package/src/event-stream.ts +42 -1
- package/src/index.ts +9 -1
- package/src/model-registry.ts +37 -3
- package/src/scope.ts +4 -0
- package/src/thread.ts +29 -1
- package/src/token-tracker.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inixiative/foundry-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"bun": ">=1.4.4"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@inixiative/archive": "^0.
|
|
36
|
+
"@inixiative/archive": "^0.34.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@inixiative/archive": {
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@inixiative/archive": "^0.
|
|
44
|
+
"@inixiative/archive": "^0.34.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -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,
|
|
@@ -412,6 +418,8 @@ export {
|
|
|
412
418
|
type FanResult,
|
|
413
419
|
Thread,
|
|
414
420
|
type ThreadAgentStatus,
|
|
421
|
+
type ThreadArtifact,
|
|
422
|
+
type ThreadArtifactKind,
|
|
415
423
|
type ThreadCheckout,
|
|
416
424
|
type ThreadConfig,
|
|
417
425
|
type ThreadContext,
|
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
|
@@ -68,7 +68,7 @@ export interface ThreadPlanItem {
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* The thread's plan, written by its agent through Foundry's `set_plan` tool. Outcomes are what the
|
|
71
|
-
* person gets (`you`: waits on the person); pains what hurts today; strategy how the work goes;
|
|
71
|
+
* person gets (`you`: waits on the person, shown in Plan, never Attention); pains what hurts today; strategy how the work goes;
|
|
72
72
|
* reasons why this way. Each technical row names, per concern (auth, permissions, db access,
|
|
73
73
|
* requests, locks, jobs, events, errors…), the existing system it uses and where, or why none fits.
|
|
74
74
|
*/
|
|
@@ -95,6 +95,26 @@ export interface ThreadPlan {
|
|
|
95
95
|
readonly updatedAt: number;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** How an artifact renders: Markdown, a Mermaid diagram, an HTML page (scripts run sandboxed) or code. */
|
|
99
|
+
export type ThreadArtifactKind = 'markdown' | 'mermaid' | 'html' | 'code';
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One artifact the thread's agent made with `set_artifact`, as the thread lists it: what it is and
|
|
103
|
+
* its versions. The content of each version is a journal row (`artifact` phase), read on demand.
|
|
104
|
+
*/
|
|
105
|
+
export interface ThreadArtifact {
|
|
106
|
+
readonly id: string;
|
|
107
|
+
readonly title: string;
|
|
108
|
+
readonly kind: ThreadArtifactKind;
|
|
109
|
+
readonly versions: readonly {
|
|
110
|
+
readonly version: number;
|
|
111
|
+
/** The turn that wrote it, when one was running: the conversation shows its card there. */
|
|
112
|
+
readonly turnId: string | null;
|
|
113
|
+
readonly at: number;
|
|
114
|
+
}[];
|
|
115
|
+
readonly updatedAt: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
98
118
|
/** One round of a thread's loop: how it went and what it found. */
|
|
99
119
|
export interface ThreadLoopRound {
|
|
100
120
|
readonly round: number;
|
|
@@ -241,6 +261,8 @@ export interface ThreadMeta {
|
|
|
241
261
|
plan?: ThreadPlan;
|
|
242
262
|
/** The process the agent repeats to reach the plan (`set_loop`, the Loop view). */
|
|
243
263
|
loop?: ThreadLoop;
|
|
264
|
+
/** What the agent made to show the person (`set_artifact`, the Artifacts view), newest last. */
|
|
265
|
+
artifacts?: ThreadArtifact[];
|
|
244
266
|
/** Where the agent says it stands: waiting on the person or blocked (`set_status`). */
|
|
245
267
|
agentStatus?: ThreadAgentStatus;
|
|
246
268
|
/** The machine (Foundry device identity, `~/.foundry/device.json`) the thread runs on. */
|
|
@@ -282,6 +304,9 @@ export interface ThreadMeta {
|
|
|
282
304
|
/** The harness running this thread's main session (`claude-code`, `codex`). Set at creation; never changed silently. */
|
|
283
305
|
worker?: string;
|
|
284
306
|
|
|
307
|
+
/** The thread role its instructions compose as (`coder`, `assistant`, …). Absent: its worker's default role. */
|
|
308
|
+
role?: string;
|
|
309
|
+
|
|
285
310
|
/** When this thread was created. */
|
|
286
311
|
readonly createdAt: number;
|
|
287
312
|
|
|
@@ -388,6 +413,8 @@ export interface ThreadConfig {
|
|
|
388
413
|
projectId?: string;
|
|
389
414
|
/** The harness running this thread's main session. */
|
|
390
415
|
worker?: string;
|
|
416
|
+
/** The thread role its instructions compose as. */
|
|
417
|
+
role?: string;
|
|
391
418
|
/** Optional token tracker — auto-records costs for every dispatch. */
|
|
392
419
|
tokenTracker?: TokenTracker;
|
|
393
420
|
}
|
|
@@ -441,6 +468,7 @@ export class Thread {
|
|
|
441
468
|
...(opts?.forkedFrom ? { forkedFrom: opts.forkedFrom } : {}),
|
|
442
469
|
projectId: opts?.projectId,
|
|
443
470
|
...(opts?.worker ? { worker: opts.worker } : {}),
|
|
471
|
+
...(opts?.role ? { role: opts.role } : {}),
|
|
444
472
|
createdAt: now,
|
|
445
473
|
lastActiveAt: now,
|
|
446
474
|
};
|
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 },
|