@inixiative/foundry-core 0.5.8 → 0.5.10
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 +4 -4
- package/src/action-prompt.ts +11 -2
- package/src/adapters/file-memory.ts +5 -0
- package/src/adapters/http-memory.ts +5 -0
- package/src/adapters/markdown-docs.ts +5 -0
- package/src/adapters/markdown-hierarchy.ts +7 -2
- package/src/adapters/sqlite-memory.ts +21 -6
- package/src/base-agent.ts +5 -0
- package/src/bounded-set.ts +5 -0
- package/src/cache-lifecycle.ts +5 -0
- package/src/capability.ts +5 -0
- package/src/clarifier.ts +5 -0
- package/src/classifier.ts +5 -0
- package/src/context-layer.ts +10 -0
- package/src/context-stack.ts +5 -0
- package/src/credentials.ts +5 -0
- package/src/decider.ts +5 -0
- package/src/delivery-evidence.ts +5 -0
- package/src/event-stream.ts +5 -0
- package/src/executor.ts +17 -4
- package/src/feedback/archive-turns.ts +25 -4
- package/src/feedback/correction.ts +13 -1
- package/src/feedback/experiment-gate.ts +5 -0
- package/src/feedback/review-findings.ts +5 -0
- package/src/feedback/warden-labels.ts +5 -0
- package/src/feedback/warden-prefilter.ts +5 -0
- package/src/feedback/warden-probes.ts +5 -0
- package/src/harness.ts +7 -2
- package/src/hydrator.ts +5 -0
- package/src/id.ts +5 -0
- package/src/index.ts +23 -11
- package/src/intervention.ts +5 -0
- package/src/messages.ts +5 -0
- package/src/middleware.ts +5 -0
- package/src/model-registry.ts +92 -2
- package/src/native-evidence.ts +18 -0
- package/src/native-tools.ts +5 -0
- package/src/retry.ts +5 -0
- package/src/router.ts +5 -0
- package/src/scope.ts +5 -0
- package/src/serialized-queue.ts +35 -0
- package/src/session-delivery.ts +5 -0
- package/src/signal.ts +5 -0
- package/src/thread.ts +196 -6
- package/src/token-counts.ts +5 -0
- package/src/token-tracker.ts +49 -24
- package/src/tools.ts +6 -1
- package/src/trace.ts +5 -0
- package/src/types.ts +5 -0
- package/src/hooks.ts +0 -133
package/src/middleware.ts
CHANGED
package/src/model-registry.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind registry
|
|
4
|
+
* @partOf primitive:modelRegistry
|
|
5
|
+
*/
|
|
1
6
|
/**
|
|
2
7
|
* The model registry: the vocabulary of what models are worth using for, the
|
|
3
8
|
* table of providers and models, and the lookups over it.
|
|
@@ -18,6 +23,7 @@ export type ProviderType =
|
|
|
18
23
|
| 'gemini'
|
|
19
24
|
| 'claude-code'
|
|
20
25
|
| 'codex'
|
|
26
|
+
| 'muse'
|
|
21
27
|
| 'xai'
|
|
22
28
|
| 'typesafe'
|
|
23
29
|
| 'custom';
|
|
@@ -82,7 +88,17 @@ export const MODEL_CAPABILITIES: readonly ModelCapability[] = [
|
|
|
82
88
|
export type ProviderCredential = 'api-key' | 'subscription' | 'local';
|
|
83
89
|
|
|
84
90
|
/** Effort levels hosts publish. A superset of CompletionOpts.thinking. */
|
|
85
|
-
export
|
|
91
|
+
export const REASONING_EFFORTS = [
|
|
92
|
+
'none',
|
|
93
|
+
'low',
|
|
94
|
+
'medium',
|
|
95
|
+
'high',
|
|
96
|
+
'xhigh',
|
|
97
|
+
'max',
|
|
98
|
+
'ultra',
|
|
99
|
+
] as const;
|
|
100
|
+
/** The one effort vocabulary: every effort setting and picker is a subset of it. */
|
|
101
|
+
export type ReasoningEffort = (typeof REASONING_EFFORTS)[number];
|
|
86
102
|
|
|
87
103
|
/**
|
|
88
104
|
* How one model takes its reasoning controls. This is the map that replaced a
|
|
@@ -195,6 +211,13 @@ const MUSE_REASONING: ModelReasoning = {
|
|
|
195
211
|
fallback: 'medium',
|
|
196
212
|
outputField: 'max_completion_tokens',
|
|
197
213
|
};
|
|
214
|
+
/** Muse Code's catalogue (`<muse data>/model-catalog`): the CLI sends `effort` on each turn, high by default.
|
|
215
|
+
* `minimal` is offered too; Foundry's effort ladder starts at low. */
|
|
216
|
+
const MUSE_CODE_REASONING: ModelReasoning = {
|
|
217
|
+
param: 'reasoning_effort',
|
|
218
|
+
efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
219
|
+
fallback: 'high',
|
|
220
|
+
};
|
|
198
221
|
|
|
199
222
|
export interface FoundryProviderInfo {
|
|
200
223
|
id: string;
|
|
@@ -236,6 +259,12 @@ export const SUBSCRIPTION_DECISION_MODELS = {
|
|
|
236
259
|
claude: DECISION_MODEL,
|
|
237
260
|
codex: 'gpt-5.6-luna',
|
|
238
261
|
} as const;
|
|
262
|
+
/** A subscription worker's main-thread model when a thread runs on a worker other than its project's default. */
|
|
263
|
+
export const SUBSCRIPTION_WORKER_MODELS = {
|
|
264
|
+
'claude-code': 'fable',
|
|
265
|
+
codex: 'gpt-6-astra',
|
|
266
|
+
muse: 'muse-wonder-1.0',
|
|
267
|
+
} as const;
|
|
239
268
|
|
|
240
269
|
/** Tag rule: every model judges. Cheap models review and route; capable models execute and advise. */
|
|
241
270
|
const FAST = ['judgment', 'learning-review', 'tool-use'] as ModelCapability[];
|
|
@@ -724,6 +753,55 @@ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
|
|
|
724
753
|
},
|
|
725
754
|
],
|
|
726
755
|
},
|
|
756
|
+
muse: {
|
|
757
|
+
id: 'muse',
|
|
758
|
+
lab: 'meta',
|
|
759
|
+
type: 'muse',
|
|
760
|
+
label: 'Muse Code (Meta subscription)',
|
|
761
|
+
description:
|
|
762
|
+
"Native Muse Code harness (`muse serve`) using the user's own Muse login. Separate from Meta's pay-as-you-go Model API.",
|
|
763
|
+
credential: 'subscription',
|
|
764
|
+
// Off until the user turns on Meta's subscription mode: few machines have Muse Code signed in.
|
|
765
|
+
enabledByDefault: false,
|
|
766
|
+
models: [
|
|
767
|
+
// From the Muse Code 1.4.14 catalogue a Muse Code Everyday Usage login is served
|
|
768
|
+
// (`<muse data>/model-catalog`, 2026-11-01). The -contributor ids are hidden there and not listed.
|
|
769
|
+
{
|
|
770
|
+
id: 'muse-wonder-1.0',
|
|
771
|
+
label: 'Muse Wonder 1.0',
|
|
772
|
+
tier: 'powerful',
|
|
773
|
+
costTier: 'high',
|
|
774
|
+
contextWindow: 1_007_997,
|
|
775
|
+
maxOutputTokens: 128_000,
|
|
776
|
+
runtimeKind: 'native-harness',
|
|
777
|
+
capabilities: POWERFUL_REASONING,
|
|
778
|
+
reasoning: MUSE_CODE_REASONING,
|
|
779
|
+
notes: 'The Muse Code default.',
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
id: 'muse-spark-1.3',
|
|
783
|
+
label: 'Muse Spark 1.3',
|
|
784
|
+
tier: 'powerful',
|
|
785
|
+
costTier: 'high',
|
|
786
|
+
contextWindow: 1_007_997,
|
|
787
|
+
maxOutputTokens: 128_000,
|
|
788
|
+
runtimeKind: 'native-harness',
|
|
789
|
+
capabilities: POWERFUL_REASONING,
|
|
790
|
+
reasoning: MUSE_CODE_REASONING,
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
id: 'muse-spark-1.2',
|
|
794
|
+
label: 'Muse Spark 1.2',
|
|
795
|
+
tier: 'powerful',
|
|
796
|
+
costTier: 'high',
|
|
797
|
+
contextWindow: 1_007_997,
|
|
798
|
+
maxOutputTokens: 128_000,
|
|
799
|
+
runtimeKind: 'native-harness',
|
|
800
|
+
capabilities: POWERFUL_REASONING,
|
|
801
|
+
reasoning: { ...MUSE_CODE_REASONING, efforts: ['low', 'medium', 'high', 'xhigh'] },
|
|
802
|
+
},
|
|
803
|
+
],
|
|
804
|
+
},
|
|
727
805
|
meta: {
|
|
728
806
|
id: 'meta',
|
|
729
807
|
lab: 'meta',
|
|
@@ -1672,7 +1750,7 @@ export const MODEL_VENDORS: readonly ModelVendor[] = [
|
|
|
1672
1750
|
id: 'meta',
|
|
1673
1751
|
label: 'Meta (Muse)',
|
|
1674
1752
|
lab: 'meta',
|
|
1675
|
-
subscription: {
|
|
1753
|
+
subscription: { provider: 'muse' },
|
|
1676
1754
|
api: { provider: 'meta' },
|
|
1677
1755
|
},
|
|
1678
1756
|
{
|
|
@@ -1723,6 +1801,18 @@ export function modelOptionsByCapability(...capabilities: ModelCapability[]): Mo
|
|
|
1723
1801
|
);
|
|
1724
1802
|
}
|
|
1725
1803
|
|
|
1804
|
+
/**
|
|
1805
|
+
* The efforts a registered model accepts, in order; undefined for a model the registry does not
|
|
1806
|
+
* list (a local server's), which callers take as "not known, don't narrow".
|
|
1807
|
+
*/
|
|
1808
|
+
export function modelEfforts(
|
|
1809
|
+
providerId: string,
|
|
1810
|
+
modelId: string,
|
|
1811
|
+
): readonly ReasoningEffort[] | undefined {
|
|
1812
|
+
const model = registryModel(providerId, modelId);
|
|
1813
|
+
return model ? (model.reasoning?.efforts ?? []) : undefined;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1726
1816
|
export function registryModel(providerId: string, modelId: string): FoundryModelInfo | undefined {
|
|
1727
1817
|
return MODEL_REGISTRY[providerId]?.models.find((model) => model.id === modelId);
|
|
1728
1818
|
}
|
package/src/native-evidence.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind transformer
|
|
4
|
+
* @partOf primitive:nativeSessions
|
|
5
|
+
*/
|
|
1
6
|
/** Public, owned projection. Raw protocol and reasoning are deliberately absent. */
|
|
2
7
|
export interface NativeOwner {
|
|
3
8
|
readonly threadId: string;
|
|
@@ -220,6 +225,8 @@ export interface NativeObservation {
|
|
|
220
225
|
observe(evidence: NativeEvidence): void | Promise<void>;
|
|
221
226
|
/** Receives the stop for this owner's registered admission, where the runtime acknowledges stops. */
|
|
222
227
|
interruptible?(interrupt: NativeInterrupt): void;
|
|
228
|
+
/** Receives how to steer more input into this owner's registered admission, or why it can't be. */
|
|
229
|
+
steerable?(steer: NativeSteer): void;
|
|
223
230
|
/**
|
|
224
231
|
* The executor's public thinking text, for live display only. It never becomes evidence:
|
|
225
232
|
* nothing here is journalled, traced or projected (reasoning stays out of NativeEvidence).
|
|
@@ -229,6 +236,17 @@ export interface NativeObservation {
|
|
|
229
236
|
/** `acknowledged`: the runtime confirmed the stop and the turn reached its native terminal. */
|
|
230
237
|
export type NativeInterruptOutcome = 'acknowledged' | 'no-turn' | 'unacknowledged';
|
|
231
238
|
export type NativeInterrupt = (opts?: { timeoutMs?: number }) => Promise<NativeInterruptOutcome>;
|
|
239
|
+
/** What became of a steer: taken into the running turn, sent but unconfirmed, or not delivered (with why). */
|
|
240
|
+
export type NativeSteerOutcome =
|
|
241
|
+
| { readonly outcome: 'steered' | 'unknown' }
|
|
242
|
+
| { readonly outcome: 'ignored'; readonly reason: string };
|
|
243
|
+
/**
|
|
244
|
+
* A running admission's steer (CORE-001): `push` adds text to that turn, where the runtime can steer.
|
|
245
|
+
* Where it can't, `push` is absent and `reason` says why, so the caller queues and says so.
|
|
246
|
+
*/
|
|
247
|
+
export type NativeSteer =
|
|
248
|
+
| { readonly push: (text: string) => Promise<NativeSteerOutcome> }
|
|
249
|
+
| { readonly push?: undefined; readonly reason: string };
|
|
232
250
|
export function freezeEvidence<T>(value: T): T {
|
|
233
251
|
const copy = structuredClone(value);
|
|
234
252
|
const freeze = (v: unknown) => {
|
package/src/native-tools.ts
CHANGED
package/src/retry.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind middleware
|
|
4
|
+
* @partOf primitive:middleware
|
|
5
|
+
*/
|
|
1
6
|
import { CapabilityDeniedError } from './capability';
|
|
2
7
|
import type { DispatchContext, Middleware, MiddlewareNext } from './middleware';
|
|
3
8
|
import { BudgetExceededError } from './token-tracker';
|
package/src/router.ts
CHANGED
package/src/scope.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind utils
|
|
4
|
+
* @partOf primitive:concurrency
|
|
5
|
+
*/
|
|
6
|
+
// Copied from the template's packages/shared/src/utils/serializedQueue.ts.
|
|
7
|
+
// Each run(fn) waits for the previous to settle before invoking fn; a failure
|
|
8
|
+
// does not poison the chain. Errors still reject the caller's promise.
|
|
9
|
+
|
|
10
|
+
export type SerializedQueue = {
|
|
11
|
+
run: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
12
|
+
size: () => number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const createSerializedQueue = (): SerializedQueue => {
|
|
16
|
+
let tail: Promise<unknown> = Promise.resolve();
|
|
17
|
+
let pending = 0;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
run: <T>(fn: () => Promise<T>): Promise<T> => {
|
|
21
|
+
pending++;
|
|
22
|
+
const wrapped = async (): Promise<T> => {
|
|
23
|
+
try {
|
|
24
|
+
return await fn();
|
|
25
|
+
} finally {
|
|
26
|
+
pending--;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const next = tail.then(wrapped, wrapped);
|
|
30
|
+
tail = next;
|
|
31
|
+
return next;
|
|
32
|
+
},
|
|
33
|
+
size: () => pending,
|
|
34
|
+
};
|
|
35
|
+
};
|
package/src/session-delivery.ts
CHANGED
package/src/signal.ts
CHANGED
package/src/thread.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf primitive:thread
|
|
5
|
+
*/
|
|
1
6
|
import type { BaseAgent, ExecutionResult } from './base-agent';
|
|
2
7
|
import { CacheLifecycle } from './cache-lifecycle';
|
|
3
8
|
import type { ContextStack, ContextStackView, LayerFilter } from './context-stack';
|
|
@@ -14,6 +19,8 @@ export type ThreadStatus = 'idle' | 'active' | 'waiting' | 'archived';
|
|
|
14
19
|
export interface ThreadName {
|
|
15
20
|
readonly text: string;
|
|
16
21
|
readonly updatedAt: number;
|
|
22
|
+
/** A Foundry name the thread's agent gave (`rename_thread`): Foundry's namer leaves it alone. */
|
|
23
|
+
readonly by?: 'agent';
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/** Tracked work a thread links to, in Archive's integration-reference shape (`github` `owner/repo#12`, `linear` `ENG-4`). */
|
|
@@ -23,6 +30,132 @@ export interface ThreadReference {
|
|
|
23
30
|
readonly url?: string;
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
/**
|
|
34
|
+
* A thread's worktree record. `mode` is how its repository makes worktrees (`template`: slots via
|
|
35
|
+
* `worktree:create`; `classic`: plain git; `none`: the thread works outside any git checkout).
|
|
36
|
+
* `released` means the recorded checkout no longer exists on disk; the rest is what it was.
|
|
37
|
+
*/
|
|
38
|
+
export interface ThreadCheckout {
|
|
39
|
+
readonly mode: 'template' | 'classic' | 'none';
|
|
40
|
+
/** Worktree root (absent for `none`). */
|
|
41
|
+
readonly path?: string;
|
|
42
|
+
/** The repository's main checkout (absent for `none`). */
|
|
43
|
+
readonly root?: string;
|
|
44
|
+
/** The repository's main checkout rather than a linked worktree. */
|
|
45
|
+
readonly main: boolean;
|
|
46
|
+
/** Template mode: the slot the worktree holds (main checkout = 0). */
|
|
47
|
+
readonly slot?: number;
|
|
48
|
+
/** The branch was squash-merged into the repository's default branch (tree equality), and when it was first seen. */
|
|
49
|
+
readonly merged?: { readonly into: string; readonly at: number };
|
|
50
|
+
/** `recreating`: the mode's create is running for a released worktree. */
|
|
51
|
+
readonly state: 'live' | 'released' | 'recreating';
|
|
52
|
+
/** The last recreate failed: the command's error. */
|
|
53
|
+
readonly error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What a thread finished: a merge of its branch, or a plan outcome marked done. */
|
|
57
|
+
export interface ThreadDone {
|
|
58
|
+
readonly kind: 'merged' | 'milestone';
|
|
59
|
+
readonly text: string;
|
|
60
|
+
readonly at: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** One item of a plan section: a short line, and its detail when the line is not enough. */
|
|
64
|
+
export interface ThreadPlanItem {
|
|
65
|
+
readonly text: string;
|
|
66
|
+
readonly detail?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
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;
|
|
72
|
+
* reasons why this way. Each technical row names, per concern (auth, permissions, db access,
|
|
73
|
+
* requests, locks, jobs, events, errors…), the existing system it uses and where, or why none fits.
|
|
74
|
+
*/
|
|
75
|
+
export interface ThreadPlan {
|
|
76
|
+
readonly outcomes: readonly (ThreadPlanItem & {
|
|
77
|
+
readonly status: 'todo' | 'doing' | 'done' | 'you';
|
|
78
|
+
})[];
|
|
79
|
+
readonly pains: readonly ThreadPlanItem[];
|
|
80
|
+
readonly strategy: readonly ThreadPlanItem[];
|
|
81
|
+
readonly reasons: readonly ThreadPlanItem[];
|
|
82
|
+
readonly technical: readonly {
|
|
83
|
+
readonly concern: string;
|
|
84
|
+
readonly decision: 'reuse' | 'extend' | 'new' | 'combine' | 'refactor' | 'migrate' | 'none';
|
|
85
|
+
readonly system: string;
|
|
86
|
+
readonly path?: string;
|
|
87
|
+
readonly reason: string;
|
|
88
|
+
}[];
|
|
89
|
+
/**
|
|
90
|
+
* Who wrote it: the thread's agent (`set_plan`), or Foundry's draft when a turn that brought new
|
|
91
|
+
* work ended with no plan recorded. A draft is shown as one and the agent's next `set_plan`
|
|
92
|
+
* replaces it whole.
|
|
93
|
+
*/
|
|
94
|
+
readonly draftedBy?: 'agent' | 'foundry';
|
|
95
|
+
readonly updatedAt: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One round of a thread's loop: how it went and what it found. */
|
|
99
|
+
export interface ThreadLoopRound {
|
|
100
|
+
readonly round: number;
|
|
101
|
+
readonly result: 'pass' | 'fail' | 'partial';
|
|
102
|
+
readonly note: string;
|
|
103
|
+
readonly at: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The thread's loop: the process the agent repeats to reach the plan's outcomes (Aron, 2026-11-01:
|
|
108
|
+
* "The plan is the goals that you want to achieve. The loop is the process you're doing to achieve
|
|
109
|
+
* them"). Recorded by the agent (`set_loop`, `loop_round`, `stop_loop`) for work that needs several
|
|
110
|
+
* rounds of build, check and fix; shown in the Loop tab.
|
|
111
|
+
*/
|
|
112
|
+
export interface ThreadLoop {
|
|
113
|
+
/** What the loop drives toward, in a line. */
|
|
114
|
+
readonly goal: string;
|
|
115
|
+
/** One round, in order: e.g. build, run the check, fix what failed. */
|
|
116
|
+
readonly steps: readonly ThreadPlanItem[];
|
|
117
|
+
/** When it stops: the check that passing means done. */
|
|
118
|
+
readonly until: string;
|
|
119
|
+
/** Rounds at most before the agent stops and asks. */
|
|
120
|
+
readonly maxRounds?: number;
|
|
121
|
+
readonly rounds: readonly ThreadLoopRound[];
|
|
122
|
+
/** `done`: the stop condition held. `stopped`: ended without it (the reason says why). */
|
|
123
|
+
readonly state: 'running' | 'done' | 'stopped';
|
|
124
|
+
readonly stopReason?: string;
|
|
125
|
+
readonly startedAt: number;
|
|
126
|
+
readonly updatedAt: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* What the thread's agent says about where it stands, when Foundry can't see it: it waits on the
|
|
131
|
+
* person (an orange "the agent asks"), or it is blocked (red). Working, queued, failed, approvals
|
|
132
|
+
* and archived are Foundry's to know and never set here; done is `ThreadMeta.done`. Cleared by the
|
|
133
|
+
* owner's next message.
|
|
134
|
+
*/
|
|
135
|
+
export interface ThreadAgentStatus {
|
|
136
|
+
readonly state: 'needs-you' | 'blocked';
|
|
137
|
+
readonly text: string;
|
|
138
|
+
readonly at: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Where a thread runs: the Foundry device it was recorded on, and that Foundry's name then. */
|
|
142
|
+
export interface ThreadMachine {
|
|
143
|
+
readonly deviceId: string;
|
|
144
|
+
readonly deviceName: string;
|
|
145
|
+
readonly installationName?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A thread snoozed by its person: quiet until `until` (ISO), then cleared. `condition` names what
|
|
150
|
+
* the time was resolved from (`quota-reset`: when the reached usage window resets). A snooze never
|
|
151
|
+
* changes the thread's status, so clearing it leaves the thread as it was.
|
|
152
|
+
*/
|
|
153
|
+
export interface ThreadSnooze {
|
|
154
|
+
readonly until: string;
|
|
155
|
+
readonly condition?: 'quota-reset';
|
|
156
|
+
readonly setAt: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
26
159
|
/** Where a thread's work lives, as the runtime last observed it. */
|
|
27
160
|
export interface ThreadContext {
|
|
28
161
|
/** `owner/repo` of the worktree's origin. */
|
|
@@ -31,19 +164,36 @@ export interface ThreadContext {
|
|
|
31
164
|
readonly branch?: string;
|
|
32
165
|
/** Worktree root containing the thread's cwd. */
|
|
33
166
|
readonly worktree?: string;
|
|
167
|
+
/** The thread's checkout as a worktree record (FEAT-003): mode, slot and whether it still exists. */
|
|
168
|
+
readonly checkout?: ThreadCheckout;
|
|
34
169
|
readonly references: readonly ThreadReference[];
|
|
35
170
|
readonly updatedAt: number;
|
|
36
171
|
}
|
|
37
172
|
|
|
38
|
-
/** The name a thread shows: a person's name overrides the
|
|
173
|
+
/** The name a thread shows: a person's name overrides the one Foundry gives it. */
|
|
39
174
|
export function threadTitle(
|
|
40
|
-
meta: Pick<ThreadMeta, 'name' | '
|
|
41
|
-
): { text: string; source: 'human' | 'agent' | 'description' } | undefined {
|
|
175
|
+
meta: Pick<ThreadMeta, 'name' | 'foundryName' | 'description'>,
|
|
176
|
+
): { text: string; source: 'human' | 'agent' | 'foundry' | 'description' } | undefined {
|
|
42
177
|
if (meta.name?.text) return { text: meta.name.text, source: 'human' };
|
|
43
|
-
if (meta.
|
|
178
|
+
if (meta.foundryName?.text)
|
|
179
|
+
return {
|
|
180
|
+
text: meta.foundryName.text,
|
|
181
|
+
source: meta.foundryName.by === 'agent' ? 'agent' : 'foundry',
|
|
182
|
+
};
|
|
44
183
|
return meta.description ? { text: meta.description, source: 'description' } : undefined;
|
|
45
184
|
}
|
|
46
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Where a forked thread came from: provenance, not nesting. A fork is a top-level thread (Aron,
|
|
188
|
+
* 2026-11-01: delegated threads are top-level; nesting is for subagents), so it is not a
|
|
189
|
+
* `parentThreadId`. Its first session is seeded with the source's conversation through `journalSeq`.
|
|
190
|
+
*/
|
|
191
|
+
export interface ThreadFork {
|
|
192
|
+
readonly threadId: string;
|
|
193
|
+
/** The source's journal seq the fork was cut at. */
|
|
194
|
+
readonly journalSeq: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
47
197
|
/** How a subagent thread came to be: who spawned it, through what, and its native identity. */
|
|
48
198
|
export interface ThreadSpawn {
|
|
49
199
|
readonly via: 'foundry' | 'native-claude' | 'native-codex' | 'host-paseo' | 'host-t3';
|
|
@@ -66,8 +216,11 @@ export interface ThreadMeta {
|
|
|
66
216
|
/** Name a person set. A hard override: agents never change it. */
|
|
67
217
|
name?: ThreadName;
|
|
68
218
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Name Foundry gives the thread from the person's messages (its first message, then again when
|
|
221
|
+
* the work moves) while no person has named it. Never set once a person has.
|
|
222
|
+
*/
|
|
223
|
+
foundryName?: ThreadName;
|
|
71
224
|
|
|
72
225
|
/** Branch, worktree and linked PRs/tickets, derived by the runtime. */
|
|
73
226
|
context?: ThreadContext;
|
|
@@ -78,6 +231,30 @@ export interface ThreadMeta {
|
|
|
78
231
|
/** Current status. */
|
|
79
232
|
status: ThreadStatus;
|
|
80
233
|
|
|
234
|
+
/**
|
|
235
|
+
* The thread's last finished piece of work, shown as its done state until the next task starts:
|
|
236
|
+
* its branch merged (`merged`), or a plan outcome reached `done` (`milestone`, with its text).
|
|
237
|
+
*/
|
|
238
|
+
done?: ThreadDone;
|
|
239
|
+
|
|
240
|
+
/** The agent's plan for the thread's work (`set_plan`, the Plan view): what, why, and how. */
|
|
241
|
+
plan?: ThreadPlan;
|
|
242
|
+
/** The process the agent repeats to reach the plan (`set_loop`, the Loop view). */
|
|
243
|
+
loop?: ThreadLoop;
|
|
244
|
+
/** Where the agent says it stands: waiting on the person or blocked (`set_status`). */
|
|
245
|
+
agentStatus?: ThreadAgentStatus;
|
|
246
|
+
/** The machine (Foundry device identity, `~/.foundry/device.json`) the thread runs on. */
|
|
247
|
+
machine?: ThreadMachine;
|
|
248
|
+
|
|
249
|
+
/** Put out of mind until a time (or a condition, resolved to its time); status is untouched. */
|
|
250
|
+
snooze?: ThreadSnooze;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* When the owner last looked at the thread (epoch ms; the viewer marks it on open and while open).
|
|
254
|
+
* A turn that completed after it is a hand-back, a "new arrival" (VIEWER-001's seen-by-Aron mark).
|
|
255
|
+
*/
|
|
256
|
+
seenAt?: number;
|
|
257
|
+
|
|
81
258
|
/** Working directory for this thread's tool execution (worktree path). */
|
|
82
259
|
cwd?: string;
|
|
83
260
|
|
|
@@ -90,6 +267,9 @@ export interface ThreadMeta {
|
|
|
90
267
|
/** The parent's turn that spawned this thread (a subagent). */
|
|
91
268
|
parentTurnId?: string;
|
|
92
269
|
|
|
270
|
+
/** The thread and point this one was forked from (provenance; a fork is top-level). */
|
|
271
|
+
forkedFrom?: ThreadFork;
|
|
272
|
+
|
|
93
273
|
/** How this subagent thread was spawned. */
|
|
94
274
|
spawn?: ThreadSpawn;
|
|
95
275
|
|
|
@@ -105,6 +285,9 @@ export interface ThreadMeta {
|
|
|
105
285
|
/** Project that owns this thread. Scopes memory sources and tool calls. */
|
|
106
286
|
projectId?: string;
|
|
107
287
|
|
|
288
|
+
/** The harness running this thread's main session (`claude-code`, `codex`). Set at creation; never changed silently. */
|
|
289
|
+
worker?: string;
|
|
290
|
+
|
|
108
291
|
/** When this thread was created. */
|
|
109
292
|
readonly createdAt: number;
|
|
110
293
|
|
|
@@ -214,8 +397,12 @@ export interface ThreadConfig {
|
|
|
214
397
|
branch?: string;
|
|
215
398
|
/** Parent thread when this thread is a subagent spawned by another. */
|
|
216
399
|
parentThreadId?: string;
|
|
400
|
+
/** The thread and point this one was forked from. */
|
|
401
|
+
forkedFrom?: ThreadFork;
|
|
217
402
|
/** Project that owns this thread. Scopes memory sources and tool calls. */
|
|
218
403
|
projectId?: string;
|
|
404
|
+
/** The harness running this thread's main session. */
|
|
405
|
+
worker?: string;
|
|
219
406
|
/** Optional token tracker — auto-records costs for every dispatch. */
|
|
220
407
|
tokenTracker?: TokenTracker;
|
|
221
408
|
}
|
|
@@ -266,7 +453,9 @@ export class Thread {
|
|
|
266
453
|
cwd: opts?.cwd,
|
|
267
454
|
branch: opts?.branch,
|
|
268
455
|
parentThreadId: opts?.parentThreadId,
|
|
456
|
+
...(opts?.forkedFrom ? { forkedFrom: opts.forkedFrom } : {}),
|
|
269
457
|
projectId: opts?.projectId,
|
|
458
|
+
...(opts?.worker ? { worker: opts.worker } : {}),
|
|
270
459
|
createdAt: now,
|
|
271
460
|
lastActiveAt: now,
|
|
272
461
|
};
|
|
@@ -450,6 +639,7 @@ export class Thread {
|
|
|
450
639
|
register: opts.nativeObservation.register,
|
|
451
640
|
observe: opts.nativeObservation.observe,
|
|
452
641
|
interruptible: opts.nativeObservation.interruptible,
|
|
642
|
+
steerable: opts.nativeObservation.steerable,
|
|
453
643
|
thought: opts.nativeObservation.thought,
|
|
454
644
|
owner: Object.freeze({
|
|
455
645
|
threadId: this.id,
|
package/src/token-counts.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind helper
|
|
4
|
+
* @partOf feature:usage
|
|
5
|
+
*/
|
|
1
6
|
/** Cache counts, when supplied, are disjoint from input. Thinking and TTL
|
|
2
7
|
* breakdowns are subsets of output/cacheWrite, never additional tokens.
|
|
3
8
|
* Missing optional counters mean unreported, not a measured zero.
|