auggy 0.3.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/CHANGELOG.md +96 -0
- package/LICENSE +201 -0
- package/README.md +161 -0
- package/package.json +76 -0
- package/src/agent-card.ts +39 -0
- package/src/agent.ts +283 -0
- package/src/agentmail-client.ts +138 -0
- package/src/augments/bash/index.ts +463 -0
- package/src/augments/bash/skill/SKILL.md +156 -0
- package/src/augments/budgets/budget-store.ts +513 -0
- package/src/augments/budgets/index.ts +134 -0
- package/src/augments/budgets/preamble.ts +93 -0
- package/src/augments/budgets/types.ts +89 -0
- package/src/augments/file-memory/index.ts +71 -0
- package/src/augments/filesystem/index.ts +533 -0
- package/src/augments/filesystem/skill/SKILL.md +142 -0
- package/src/augments/filesystem/skill/references/mount-permissions.md +81 -0
- package/src/augments/layered-memory/extractor/buffer.ts +56 -0
- package/src/augments/layered-memory/extractor/frequency.ts +79 -0
- package/src/augments/layered-memory/extractor/inject-handler.ts +103 -0
- package/src/augments/layered-memory/extractor/parse.ts +75 -0
- package/src/augments/layered-memory/extractor/prompt.md +26 -0
- package/src/augments/layered-memory/index.ts +757 -0
- package/src/augments/layered-memory/skill/SKILL.md +153 -0
- package/src/augments/layered-memory/storage/migrations/README.md +16 -0
- package/src/augments/layered-memory/storage/migrations/supabase-add-fact-fields.sql +9 -0
- package/src/augments/layered-memory/storage/sqlite-store.ts +352 -0
- package/src/augments/layered-memory/storage/supabase-store.ts +263 -0
- package/src/augments/layered-memory/storage/types.ts +98 -0
- package/src/augments/link/index.ts +489 -0
- package/src/augments/link/translate.ts +261 -0
- package/src/augments/notify/adapters/agentmail.ts +70 -0
- package/src/augments/notify/adapters/telegram.ts +60 -0
- package/src/augments/notify/adapters/webhook.ts +55 -0
- package/src/augments/notify/index.ts +284 -0
- package/src/augments/notify/skill/SKILL.md +150 -0
- package/src/augments/org-context/index.ts +721 -0
- package/src/augments/org-context/skill/SKILL.md +96 -0
- package/src/augments/skills/index.ts +103 -0
- package/src/augments/supabase-memory/index.ts +151 -0
- package/src/augments/telegram-transport/index.ts +312 -0
- package/src/augments/telegram-transport/polling.ts +55 -0
- package/src/augments/telegram-transport/webhook.ts +56 -0
- package/src/augments/turn-control/index.ts +61 -0
- package/src/augments/turn-control/skill/SKILL.md +155 -0
- package/src/augments/visitor-auth/email-validation.ts +66 -0
- package/src/augments/visitor-auth/index.ts +779 -0
- package/src/augments/visitor-auth/rate-limiter.ts +90 -0
- package/src/augments/visitor-auth/skill/SKILL.md +55 -0
- package/src/augments/visitor-auth/storage/sqlite-store.ts +398 -0
- package/src/augments/visitor-auth/storage/types.ts +164 -0
- package/src/augments/visitor-auth/types.ts +123 -0
- package/src/augments/visitor-auth/verify-page.ts +179 -0
- package/src/augments/web-fetch/index.ts +331 -0
- package/src/augments/web-fetch/skill/SKILL.md +100 -0
- package/src/cli/agent-index.ts +289 -0
- package/src/cli/augment-catalog.ts +320 -0
- package/src/cli/augment-resolver.ts +597 -0
- package/src/cli/commands/add-skill.ts +194 -0
- package/src/cli/commands/add.ts +87 -0
- package/src/cli/commands/chat.ts +207 -0
- package/src/cli/commands/create.ts +462 -0
- package/src/cli/commands/dev.ts +139 -0
- package/src/cli/commands/eval.ts +180 -0
- package/src/cli/commands/ls.ts +66 -0
- package/src/cli/commands/remove.ts +95 -0
- package/src/cli/commands/restart.ts +40 -0
- package/src/cli/commands/start.ts +123 -0
- package/src/cli/commands/status.ts +104 -0
- package/src/cli/commands/stop.ts +84 -0
- package/src/cli/commands/visitors-revoke.ts +155 -0
- package/src/cli/commands/visitors.ts +101 -0
- package/src/cli/config-parser.ts +1034 -0
- package/src/cli/engine-resolver.ts +68 -0
- package/src/cli/index.ts +178 -0
- package/src/cli/model-picker.ts +89 -0
- package/src/cli/pid-registry.ts +146 -0
- package/src/cli/plist-generator.ts +117 -0
- package/src/cli/resolve-config.ts +56 -0
- package/src/cli/scaffold-skills.ts +158 -0
- package/src/cli/scaffold.ts +291 -0
- package/src/cli/skill-frontmatter.ts +51 -0
- package/src/cli/skill-validator.ts +151 -0
- package/src/cli/types.ts +228 -0
- package/src/cli/yaml-helpers.ts +66 -0
- package/src/engines/_shared/cost.ts +55 -0
- package/src/engines/_shared/schema-normalize.ts +75 -0
- package/src/engines/anthropic/pricing.ts +117 -0
- package/src/engines/anthropic.ts +483 -0
- package/src/engines/openai/pricing.ts +67 -0
- package/src/engines/openai.ts +446 -0
- package/src/engines/openrouter/pricing.ts +83 -0
- package/src/engines/openrouter.ts +185 -0
- package/src/helpers.ts +24 -0
- package/src/http.ts +387 -0
- package/src/index.ts +165 -0
- package/src/kernel/capability-table.ts +172 -0
- package/src/kernel/context-allocator.ts +161 -0
- package/src/kernel/history-manager.ts +198 -0
- package/src/kernel/lifecycle-manager.ts +106 -0
- package/src/kernel/output-validator.ts +35 -0
- package/src/kernel/preamble.ts +23 -0
- package/src/kernel/route-collector.ts +97 -0
- package/src/kernel/timeout.ts +21 -0
- package/src/kernel/tool-selector.ts +47 -0
- package/src/kernel/trace-emitter.ts +66 -0
- package/src/kernel/transport-queue.ts +147 -0
- package/src/kernel/turn-loop.ts +1148 -0
- package/src/memory/context-synthesis.ts +83 -0
- package/src/memory/memory-bus.ts +61 -0
- package/src/memory/registry.ts +80 -0
- package/src/memory/tools.ts +320 -0
- package/src/memory/types.ts +8 -0
- package/src/parts.ts +30 -0
- package/src/scaffold-templates/identity.md +31 -0
- package/src/telegram-client.ts +145 -0
- package/src/tokenizer.ts +14 -0
- package/src/transports/ag-ui-events.ts +253 -0
- package/src/transports/visitor-token.ts +82 -0
- package/src/transports/web-transport.ts +948 -0
- package/src/types.ts +1009 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// === Core types ===
|
|
2
|
+
export type {
|
|
3
|
+
// Context
|
|
4
|
+
Augment,
|
|
5
|
+
AugmentCapability,
|
|
6
|
+
AugmentConstraints,
|
|
7
|
+
AgentConfig,
|
|
8
|
+
AgentHandle,
|
|
9
|
+
AgentHealth,
|
|
10
|
+
ContextBlock,
|
|
11
|
+
ContextPlacement,
|
|
12
|
+
ContextProvenance,
|
|
13
|
+
ContextPriority,
|
|
14
|
+
EvictionPolicy,
|
|
15
|
+
ContextOrigin,
|
|
16
|
+
// A2A-compatible content
|
|
17
|
+
Part,
|
|
18
|
+
TaskState,
|
|
19
|
+
// Memory
|
|
20
|
+
MemoryDefaults,
|
|
21
|
+
MemoryEntry,
|
|
22
|
+
MemoryProviderSpec,
|
|
23
|
+
StaticMemoryProvider,
|
|
24
|
+
NamespaceMemoryProvider,
|
|
25
|
+
// Tools
|
|
26
|
+
Tool,
|
|
27
|
+
ToolCategory,
|
|
28
|
+
ToolDefinition,
|
|
29
|
+
// Peers
|
|
30
|
+
PeerIdentity,
|
|
31
|
+
PeerKind,
|
|
32
|
+
TrustLevel,
|
|
33
|
+
// Turns
|
|
34
|
+
TurnTrigger,
|
|
35
|
+
TurnTriggerType,
|
|
36
|
+
TurnState,
|
|
37
|
+
TurnResult,
|
|
38
|
+
OutboundMessage,
|
|
39
|
+
InboundMessage,
|
|
40
|
+
Message,
|
|
41
|
+
MessageRole,
|
|
42
|
+
ToolCallRecord,
|
|
43
|
+
// ADR-027 — post-turn background work
|
|
44
|
+
Transcript,
|
|
45
|
+
SchedulerContext,
|
|
46
|
+
// Kernel events
|
|
47
|
+
KernelEvent,
|
|
48
|
+
KernelEventHandler,
|
|
49
|
+
// Model
|
|
50
|
+
ModelClient,
|
|
51
|
+
ModelResponse,
|
|
52
|
+
AssembledPrompt,
|
|
53
|
+
// Storage
|
|
54
|
+
Storage,
|
|
55
|
+
// Transport
|
|
56
|
+
TransportSpec,
|
|
57
|
+
TransportKernel,
|
|
58
|
+
// Agent Card
|
|
59
|
+
AgentCard,
|
|
60
|
+
AgentCardProvider,
|
|
61
|
+
AgentCardCapabilities,
|
|
62
|
+
AgentCardSkill,
|
|
63
|
+
// Traces
|
|
64
|
+
TurnTrace,
|
|
65
|
+
// History
|
|
66
|
+
CompactionStrategy,
|
|
67
|
+
} from "./types";
|
|
68
|
+
|
|
69
|
+
// === Agent definition ===
|
|
70
|
+
export { defineAgent } from "./agent";
|
|
71
|
+
|
|
72
|
+
// === Agent Card ===
|
|
73
|
+
export { generateAgentCard } from "./agent-card";
|
|
74
|
+
|
|
75
|
+
// === Helpers ===
|
|
76
|
+
export { defineAugment, defineTool } from "./helpers";
|
|
77
|
+
export { extractText, textPart, dataPart } from "./parts";
|
|
78
|
+
|
|
79
|
+
// === Tokenizer (for augment authors who need token counting) ===
|
|
80
|
+
export { createTokenizer } from "./tokenizer";
|
|
81
|
+
|
|
82
|
+
// === Built-in augments ===
|
|
83
|
+
export { fileMemory } from "./augments/file-memory";
|
|
84
|
+
export type { FileMemoryOptions } from "./augments/file-memory";
|
|
85
|
+
export { supabaseMemory } from "./augments/supabase-memory";
|
|
86
|
+
export type { SupabaseMemoryOptions } from "./augments/supabase-memory";
|
|
87
|
+
export { filesystem } from "./augments/filesystem";
|
|
88
|
+
export type { FilesystemOptions, FsMount } from "./augments/filesystem";
|
|
89
|
+
|
|
90
|
+
// === Built-in augments (org) ===
|
|
91
|
+
export { orgContext } from "./augments/org-context";
|
|
92
|
+
export type { OrgContextOptions } from "./augments/org-context";
|
|
93
|
+
|
|
94
|
+
// === Built-in augments (web) ===
|
|
95
|
+
export { webFetch } from "./augments/web-fetch";
|
|
96
|
+
export type { WebFetchOptions, WebFetchResult } from "./augments/web-fetch";
|
|
97
|
+
|
|
98
|
+
export { bash } from "./augments/bash";
|
|
99
|
+
export type {
|
|
100
|
+
BashAugmentOptions,
|
|
101
|
+
BashRiskLevel,
|
|
102
|
+
BashScript,
|
|
103
|
+
} from "./augments/bash";
|
|
104
|
+
|
|
105
|
+
// === HTTP client (for augment authors who need HTTP) ===
|
|
106
|
+
export { createHttpClient } from "./http";
|
|
107
|
+
export type {
|
|
108
|
+
HttpClient,
|
|
109
|
+
HttpClientOptions,
|
|
110
|
+
HttpRequestInit,
|
|
111
|
+
HttpResponse,
|
|
112
|
+
} from "./http";
|
|
113
|
+
|
|
114
|
+
// === Built-in transports ===
|
|
115
|
+
export { webTransport } from "./transports/web-transport";
|
|
116
|
+
export type { WebTransportOptions } from "./transports/web-transport";
|
|
117
|
+
|
|
118
|
+
// === Engines (model client adapters) ===
|
|
119
|
+
export { createAnthropicEngine } from "./engines/anthropic";
|
|
120
|
+
export type { AnthropicEngineOptions } from "./engines/anthropic";
|
|
121
|
+
export { createOpenAIEngine } from "./engines/openai";
|
|
122
|
+
export type { OpenAIEngineOptions } from "./engines/openai";
|
|
123
|
+
export {
|
|
124
|
+
assembleOpenAISystemMessage,
|
|
125
|
+
buildOpenAIModelResponse,
|
|
126
|
+
convertOpenAIMessages,
|
|
127
|
+
convertOpenAITools,
|
|
128
|
+
safeParseJson as openaiSafeParseJson,
|
|
129
|
+
safeParseToolCall as openaiSafeParseToolCall,
|
|
130
|
+
} from "./engines/openai";
|
|
131
|
+
export { createOpenRouterEngine } from "./engines/openrouter";
|
|
132
|
+
export type {
|
|
133
|
+
OpenRouterEngineOptions,
|
|
134
|
+
OpenRouterProviderRouting,
|
|
135
|
+
} from "./engines/openrouter";
|
|
136
|
+
|
|
137
|
+
// === AG-UI event protocol (for custom transports or advanced consumers) ===
|
|
138
|
+
export {
|
|
139
|
+
runStarted,
|
|
140
|
+
runFinished,
|
|
141
|
+
runError,
|
|
142
|
+
textMessageStart,
|
|
143
|
+
textMessageContent,
|
|
144
|
+
textMessageEnd,
|
|
145
|
+
toolCallStart,
|
|
146
|
+
toolCallArgs,
|
|
147
|
+
toolCallEnd,
|
|
148
|
+
toolCallResult,
|
|
149
|
+
translateKernelEvent,
|
|
150
|
+
serializeSSE,
|
|
151
|
+
} from "./transports/ag-ui-events";
|
|
152
|
+
export type {
|
|
153
|
+
AGUIEvent,
|
|
154
|
+
AGUIBaseEvent,
|
|
155
|
+
AGUIRunStarted,
|
|
156
|
+
AGUIRunFinished,
|
|
157
|
+
AGUIRunError,
|
|
158
|
+
AGUITextMessageStart,
|
|
159
|
+
AGUITextMessageContent,
|
|
160
|
+
AGUITextMessageEnd,
|
|
161
|
+
AGUIToolCallStart,
|
|
162
|
+
AGUIToolCallArgs,
|
|
163
|
+
AGUIToolCallEnd,
|
|
164
|
+
AGUIToolCallResult,
|
|
165
|
+
} from "./transports/ag-ui-events";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Augment, PeerIdentity, TrustLevel, TurnState } from "../types";
|
|
2
|
+
|
|
3
|
+
const KERNEL_DEFAULT_MAX_TOOL_CALLS = 5;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Null peer means "no external initiator" — an internal/scheduled trigger
|
|
7
|
+
* fired by the agent's own configuration. Treated as operator for capability
|
|
8
|
+
* checks: if the operator scheduled it, the operator authorized it.
|
|
9
|
+
*/
|
|
10
|
+
export function effectiveTrustLevel(peer: PeerIdentity | null): TrustLevel {
|
|
11
|
+
return peer?.trustLevel ?? "creator";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CapabilityTable {
|
|
15
|
+
canExpose(toolName: string, turn: TurnState): boolean;
|
|
16
|
+
canExecute(
|
|
17
|
+
toolName: string,
|
|
18
|
+
input: unknown,
|
|
19
|
+
turn: TurnState,
|
|
20
|
+
): { allowed: true } | { needsApproval: true; reason: string } | { denied: true; reason: string };
|
|
21
|
+
recordToolCall(toolName: string): void;
|
|
22
|
+
resetTurn(): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createCapabilityTable(augments: Augment[]): CapabilityTable {
|
|
26
|
+
const neverExpose = new Set<string>();
|
|
27
|
+
const requiresApproval = new Set<string>();
|
|
28
|
+
// Per-trust-level additive constraints. Applied on top of the global sets.
|
|
29
|
+
const perLevelNeverExpose = new Map<TrustLevel, Set<string>>();
|
|
30
|
+
const perLevelRequiresApproval = new Map<TrustLevel, Set<string>>();
|
|
31
|
+
|
|
32
|
+
// Map tool name → augment name
|
|
33
|
+
const toolOwner = new Map<string, string>();
|
|
34
|
+
// Map augment name → max tool calls (kernel default if unset)
|
|
35
|
+
const augmentLimits = new Map<string, number>();
|
|
36
|
+
// Per-turn counters: augment name → calls this turn
|
|
37
|
+
const augmentCallCounts = new Map<string, number>();
|
|
38
|
+
// Global limit (sum of all augment limits or kernel default * augment count)
|
|
39
|
+
let globalLimit = 0;
|
|
40
|
+
let globalCalls = 0;
|
|
41
|
+
|
|
42
|
+
for (const aug of augments) {
|
|
43
|
+
const c = aug.constraints;
|
|
44
|
+
if (c) {
|
|
45
|
+
for (const tool of c.neverExpose ?? []) neverExpose.add(tool);
|
|
46
|
+
for (const tool of c.requiresHumanApproval ?? []) requiresApproval.add(tool);
|
|
47
|
+
|
|
48
|
+
if (c.perTrustLevel) {
|
|
49
|
+
for (const [level, rules] of Object.entries(c.perTrustLevel) as [
|
|
50
|
+
TrustLevel,
|
|
51
|
+
{ neverExpose?: string[]; requiresHumanApproval?: string[] },
|
|
52
|
+
][]) {
|
|
53
|
+
if (!rules) continue;
|
|
54
|
+
for (const tool of rules.neverExpose ?? []) {
|
|
55
|
+
let set = perLevelNeverExpose.get(level);
|
|
56
|
+
if (!set) {
|
|
57
|
+
set = new Set<string>();
|
|
58
|
+
perLevelNeverExpose.set(level, set);
|
|
59
|
+
}
|
|
60
|
+
set.add(tool);
|
|
61
|
+
}
|
|
62
|
+
for (const tool of rules.requiresHumanApproval ?? []) {
|
|
63
|
+
let set = perLevelRequiresApproval.get(level);
|
|
64
|
+
if (!set) {
|
|
65
|
+
set = new Set<string>();
|
|
66
|
+
perLevelRequiresApproval.set(level, set);
|
|
67
|
+
}
|
|
68
|
+
set.add(tool);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const limit = c?.maxToolCallsPerTurn ?? KERNEL_DEFAULT_MAX_TOOL_CALLS;
|
|
75
|
+
augmentLimits.set(aug.name, limit);
|
|
76
|
+
globalLimit += limit;
|
|
77
|
+
|
|
78
|
+
for (const tool of aug.tools ?? []) {
|
|
79
|
+
toolOwner.set(tool.name, aug.name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If no augments have tools, use kernel default as global limit
|
|
84
|
+
if (globalLimit === 0) globalLimit = KERNEL_DEFAULT_MAX_TOOL_CALLS;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
canExpose(toolName: string, turn: TurnState): boolean {
|
|
88
|
+
// Global block (applies to every trust level, no escape).
|
|
89
|
+
if (neverExpose.has(toolName)) return false;
|
|
90
|
+
// Per-trust-level block (applies only to that level).
|
|
91
|
+
const level = effectiveTrustLevel(turn.peer);
|
|
92
|
+
if (perLevelNeverExpose.get(level)?.has(toolName)) return false;
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
canExecute(toolName: string, _input: unknown, turn: TurnState) {
|
|
97
|
+
const level = effectiveTrustLevel(turn.peer);
|
|
98
|
+
|
|
99
|
+
// Structural denial: tools in neverExpose cannot execute, regardless
|
|
100
|
+
// of whether they appeared in the model's tool list. canExpose is a
|
|
101
|
+
// pre-flight filter that shapes the catalog shown to the model;
|
|
102
|
+
// canExecute must re-enforce the same rule here because the turn
|
|
103
|
+
// loop resolves tool calls against the full tool registry — if the
|
|
104
|
+
// model fabricates a tool name that happens to match a withheld
|
|
105
|
+
// tool, without this check the call would run.
|
|
106
|
+
if (neverExpose.has(toolName)) {
|
|
107
|
+
return {
|
|
108
|
+
denied: true,
|
|
109
|
+
reason: `Tool "${toolName}" is blocked (neverExpose)`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (perLevelNeverExpose.get(level)?.has(toolName)) {
|
|
113
|
+
return {
|
|
114
|
+
denied: true,
|
|
115
|
+
reason: `Tool "${toolName}" is not available at trust level "${level}"`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Global limit
|
|
120
|
+
if (globalCalls >= globalLimit) {
|
|
121
|
+
return {
|
|
122
|
+
denied: true,
|
|
123
|
+
reason: `Global max tool calls per turn (${globalLimit}) exceeded`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Per-augment limit
|
|
128
|
+
const owner = toolOwner.get(toolName);
|
|
129
|
+
if (owner) {
|
|
130
|
+
const limit = augmentLimits.get(owner) ?? KERNEL_DEFAULT_MAX_TOOL_CALLS;
|
|
131
|
+
const count = augmentCallCounts.get(owner) ?? 0;
|
|
132
|
+
if (count >= limit) {
|
|
133
|
+
return {
|
|
134
|
+
denied: true,
|
|
135
|
+
reason: `Max tool calls for augment "${owner}" (${limit}) exceeded`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Global approval gate (applies to every trust level).
|
|
141
|
+
if (requiresApproval.has(toolName)) {
|
|
142
|
+
return {
|
|
143
|
+
needsApproval: true,
|
|
144
|
+
reason: `Tool "${toolName}" requires human approval`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Per-trust-level approval gate (applies only to that level).
|
|
149
|
+
if (perLevelRequiresApproval.get(level)?.has(toolName)) {
|
|
150
|
+
return {
|
|
151
|
+
needsApproval: true,
|
|
152
|
+
reason: `Tool "${toolName}" requires human approval for peer trust level "${level}"`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { allowed: true };
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
recordToolCall(toolName: string) {
|
|
160
|
+
globalCalls++;
|
|
161
|
+
const owner = toolOwner.get(toolName);
|
|
162
|
+
if (owner) {
|
|
163
|
+
augmentCallCounts.set(owner, (augmentCallCounts.get(owner) ?? 0) + 1);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
resetTurn() {
|
|
168
|
+
globalCalls = 0;
|
|
169
|
+
augmentCallCounts.clear();
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssembledPrompt,
|
|
3
|
+
ContextBlock,
|
|
4
|
+
ContextPriority,
|
|
5
|
+
Message,
|
|
6
|
+
ToolDefinition,
|
|
7
|
+
} from "../types";
|
|
8
|
+
import type { Tokenizer } from "../tokenizer";
|
|
9
|
+
|
|
10
|
+
const PRIORITY_ORDER: ContextPriority[] = ["required", "high", "normal", "low", "evictable"];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Phase 1b Task 8: per-entry origin → provenance marker mapping.
|
|
14
|
+
*
|
|
15
|
+
* synthesizeContextFor stamps each ContextBlock with the per-entry
|
|
16
|
+
* origin (or the provider's defaults.origin when an entry has none).
|
|
17
|
+
* Each block represents one memory entry — multiple blocks from the
|
|
18
|
+
* same provider may carry different origin values when entries were
|
|
19
|
+
* written by different write paths (e.g. memory_write from the model
|
|
20
|
+
* vs. auto-save from the layered-memory extractor).
|
|
21
|
+
*
|
|
22
|
+
* The marker map honors the canonical OriginValue union from the
|
|
23
|
+
* storage layer ("operator" | "peer-derived" | "agent-derived" |
|
|
24
|
+
* "agent") plus the kernel's ContextOrigin ("system" + the above).
|
|
25
|
+
* Both "agent" (model wrote it) and "agent-derived" (auto-save
|
|
26
|
+
* extractor wrote it) render as [AGENT-DERIVED] — the skill teaches
|
|
27
|
+
* the model to treat both as paraphrase / self-note. Operator and
|
|
28
|
+
* system origins remain unmarked at v1.0; the preamble already
|
|
29
|
+
* teaches behavioral guidance for [PEER-DERIVED] and [AGENT-DERIVED]
|
|
30
|
+
* only, so introducing [OPERATOR] / [SYSTEM] without paired preamble
|
|
31
|
+
* guidance would just be noise to the model.
|
|
32
|
+
*
|
|
33
|
+
* Unknown origin values render unmarked (forward-compat: a future
|
|
34
|
+
* OriginValue extension ships marker + preamble guidance together).
|
|
35
|
+
*/
|
|
36
|
+
function originMarker(origin: ContextBlock["origin"] | string | undefined): string {
|
|
37
|
+
switch (origin) {
|
|
38
|
+
case "peer-derived":
|
|
39
|
+
return "[PEER-DERIVED]";
|
|
40
|
+
case "agent":
|
|
41
|
+
case "agent-derived":
|
|
42
|
+
return "[AGENT-DERIVED]";
|
|
43
|
+
default:
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ContextAllocatorConfig {
|
|
49
|
+
maxTokens: number;
|
|
50
|
+
historyPercent: number;
|
|
51
|
+
toolSchemaPercent: number;
|
|
52
|
+
tokenizer: Tokenizer;
|
|
53
|
+
preamble: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createContextAllocator(config: ContextAllocatorConfig) {
|
|
57
|
+
const preambleTokens = config.tokenizer.count(config.preamble);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
assemble(
|
|
61
|
+
augmentBlocks: ContextBlock[],
|
|
62
|
+
history: Message[],
|
|
63
|
+
tools: ToolDefinition[],
|
|
64
|
+
opts?: { toolChoice?: AssembledPrompt["toolChoice"] },
|
|
65
|
+
): AssembledPrompt {
|
|
66
|
+
const historyBudget = Math.floor(config.maxTokens * (config.historyPercent / 100));
|
|
67
|
+
const toolBudget = Math.floor(config.maxTokens * (config.toolSchemaPercent / 100));
|
|
68
|
+
|
|
69
|
+
// Count actual tool schema tokens
|
|
70
|
+
const toolSchemaTokens = tools.reduce((sum, t) => {
|
|
71
|
+
const schemaStr = JSON.stringify(t);
|
|
72
|
+
return sum + config.tokenizer.count(schemaStr);
|
|
73
|
+
}, 0);
|
|
74
|
+
|
|
75
|
+
// If tools exceed their budget, they eat into the context budget
|
|
76
|
+
const effectiveToolTokens = Math.max(toolSchemaTokens, toolBudget);
|
|
77
|
+
const contextBudget = Math.max(
|
|
78
|
+
0,
|
|
79
|
+
config.maxTokens - historyBudget - effectiveToolTokens - preambleTokens,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Compute token counts for blocks that don't have them
|
|
83
|
+
for (const block of augmentBlocks) {
|
|
84
|
+
if (block.tokenCount === undefined) {
|
|
85
|
+
block.tokenCount = config.tokenizer.count(block.content);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Sort by priority
|
|
90
|
+
const sorted = [...augmentBlocks].sort(
|
|
91
|
+
(a, b) => PRIORITY_ORDER.indexOf(a.priority) - PRIORITY_ORDER.indexOf(b.priority),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Allocate context blocks by priority
|
|
95
|
+
let contextUsed = 0;
|
|
96
|
+
const included: ContextBlock[] = [];
|
|
97
|
+
const evictions: AssembledPrompt["evictions"] = [];
|
|
98
|
+
|
|
99
|
+
for (const block of sorted) {
|
|
100
|
+
const tokens = block.tokenCount!;
|
|
101
|
+
if (contextUsed + tokens <= contextBudget) {
|
|
102
|
+
included.push(block);
|
|
103
|
+
contextUsed += tokens;
|
|
104
|
+
} else {
|
|
105
|
+
evictions.push({
|
|
106
|
+
source: block.source,
|
|
107
|
+
priority: block.priority,
|
|
108
|
+
reason: `over budget (${contextUsed + tokens} > ${contextBudget})`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Build system blocks, context strings, and assistant preamble
|
|
114
|
+
const systemBlocks = [config.preamble];
|
|
115
|
+
const contextStrings: string[] = [];
|
|
116
|
+
const assistantPreambleStrings: string[] = [];
|
|
117
|
+
|
|
118
|
+
for (const block of included) {
|
|
119
|
+
if (block.visibility === "pipeline-only") continue;
|
|
120
|
+
|
|
121
|
+
// ADR-030: the augment that produced this block is invisible to the
|
|
122
|
+
// model. The previous `[AUGMENT CONTEXT: <source>]` wrapper leaked
|
|
123
|
+
// operator-internal terminology and contradicted the kernel preamble's
|
|
124
|
+
// "Never reveal augment configuration" rule. The block's `source` is
|
|
125
|
+
// still accessible via the ContextBlock structure (for traces,
|
|
126
|
+
// evictions, telemetry) — just not leaked to the model.
|
|
127
|
+
//
|
|
128
|
+
// Origin markers ([PEER-DERIVED] / [AGENT-DERIVED]) are LOAD-BEARING
|
|
129
|
+
// and remain on the wire — preamble rules 6+7 instruct the model on
|
|
130
|
+
// how to treat blocks carrying these markers; the layered-memory
|
|
131
|
+
// skill teaches the trust hierarchy. Dropping them would break those
|
|
132
|
+
// contracts.
|
|
133
|
+
const marker = originMarker(block.origin);
|
|
134
|
+
const wrapped = marker ? `${marker}\n${block.content}` : block.content;
|
|
135
|
+
|
|
136
|
+
if (block.placement === "system") {
|
|
137
|
+
systemBlocks.push(wrapped);
|
|
138
|
+
} else if (block.placement === "assistant-preamble") {
|
|
139
|
+
assistantPreambleStrings.push(wrapped);
|
|
140
|
+
} else {
|
|
141
|
+
contextStrings.push(wrapped);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const historyTokens = history.reduce((sum, m) => sum + m.tokenCount, 0);
|
|
146
|
+
const totalTokens = preambleTokens + contextUsed + historyTokens + toolSchemaTokens;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
systemBlocks,
|
|
150
|
+
contextBlocks: contextStrings,
|
|
151
|
+
assistantPreamble:
|
|
152
|
+
assistantPreambleStrings.length > 0 ? assistantPreambleStrings : undefined,
|
|
153
|
+
messages: history,
|
|
154
|
+
tools,
|
|
155
|
+
toolChoice: opts?.toolChoice,
|
|
156
|
+
totalTokens,
|
|
157
|
+
evictions,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Message,
|
|
3
|
+
Storage,
|
|
4
|
+
CompactionStrategy,
|
|
5
|
+
Transcript,
|
|
6
|
+
PeerIdentity,
|
|
7
|
+
Part,
|
|
8
|
+
ToolCallRecord,
|
|
9
|
+
} from "../types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Per-turn snapshot recorded by the kernel at turn completion. Used by
|
|
13
|
+
* SchedulerContext.getCompletedTranscript() (ADR-027). Kept distinct from
|
|
14
|
+
* the running Message[] history because the snapshot's identity boundary
|
|
15
|
+
* is the turn, not the message — and downstream consumers (post-turn
|
|
16
|
+
* extraction) want a self-contained record they can hand to a focused
|
|
17
|
+
* extraction model without traversing the message log.
|
|
18
|
+
*/
|
|
19
|
+
export interface TurnSnapshot {
|
|
20
|
+
turnId: string;
|
|
21
|
+
threadId: string;
|
|
22
|
+
peer: PeerIdentity | null;
|
|
23
|
+
parts: Part[];
|
|
24
|
+
toolCalls: ToolCallRecord[];
|
|
25
|
+
startedAt: number;
|
|
26
|
+
endedAt: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface HistoryManager {
|
|
30
|
+
append(message: Message): void;
|
|
31
|
+
getHistory(tokenBudget: number): Message[];
|
|
32
|
+
compact(tokenBudget: number, strategy: CompactionStrategy): void;
|
|
33
|
+
save(storage: Storage): Promise<void>;
|
|
34
|
+
restore(storage: Storage): Promise<void>;
|
|
35
|
+
totalTokens(): number;
|
|
36
|
+
/**
|
|
37
|
+
* Record a per-turn snapshot at turn completion. Called by the turn-loop
|
|
38
|
+
* before the SchedulerContext closure binds (ADR-027). Multiple snapshots
|
|
39
|
+
* for the same turnId overwrite (idempotent on retry).
|
|
40
|
+
*/
|
|
41
|
+
recordTurn(snapshot: TurnSnapshot): void;
|
|
42
|
+
/**
|
|
43
|
+
* Retrieve a previously-recorded turn snapshot. Returns null if the
|
|
44
|
+
* snapshot was never recorded (e.g. turn errored before recording) or
|
|
45
|
+
* has been evicted by retention. Kernel-internal: SchedulerContext
|
|
46
|
+
* exposes only the just-completed turn via a closure-bound wrapper.
|
|
47
|
+
*/
|
|
48
|
+
getTranscript(turnId: string): Transcript | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Cap on retained per-turn snapshots. ADR-027 only requires retaining the
|
|
53
|
+
* just-completed turn; we keep the last N to absorb scheduleAfterTurn
|
|
54
|
+
* hooks that may take a brief moment to read after the next turn admits.
|
|
55
|
+
* Bounded to prevent unbounded growth on long threads.
|
|
56
|
+
*/
|
|
57
|
+
const MAX_TURN_SNAPSHOTS = 32;
|
|
58
|
+
|
|
59
|
+
export function createHistoryManager(opts: { threadId: string }): HistoryManager {
|
|
60
|
+
let messages: Message[] = [];
|
|
61
|
+
let runningTokens = 0;
|
|
62
|
+
const storageKey = `history:${opts.threadId}`;
|
|
63
|
+
// Insertion-ordered snapshot store. JS Map preserves insertion order;
|
|
64
|
+
// we evict oldest when capacity is exceeded.
|
|
65
|
+
const turnSnapshots = new Map<string, TurnSnapshot>();
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
append(message: Message) {
|
|
69
|
+
messages.push(message);
|
|
70
|
+
runningTokens += message.tokenCount;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
recordTurn(snapshot: TurnSnapshot) {
|
|
74
|
+
// Re-recording the same turnId moves it to most-recent without
|
|
75
|
+
// increasing capacity pressure.
|
|
76
|
+
if (turnSnapshots.has(snapshot.turnId)) {
|
|
77
|
+
turnSnapshots.delete(snapshot.turnId);
|
|
78
|
+
} else if (turnSnapshots.size >= MAX_TURN_SNAPSHOTS) {
|
|
79
|
+
// Evict oldest insertion-order entry.
|
|
80
|
+
const oldestKey = turnSnapshots.keys().next().value;
|
|
81
|
+
if (oldestKey !== undefined) turnSnapshots.delete(oldestKey);
|
|
82
|
+
}
|
|
83
|
+
turnSnapshots.set(snapshot.turnId, snapshot);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
getTranscript(turnId: string): Transcript | null {
|
|
87
|
+
const snap = turnSnapshots.get(turnId);
|
|
88
|
+
if (!snap) return null;
|
|
89
|
+
return {
|
|
90
|
+
turnId: snap.turnId,
|
|
91
|
+
threadId: snap.threadId,
|
|
92
|
+
peer: snap.peer,
|
|
93
|
+
parts: snap.parts,
|
|
94
|
+
toolCalls: snap.toolCalls,
|
|
95
|
+
startedAt: snap.startedAt,
|
|
96
|
+
endedAt: snap.endedAt,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
getHistory(tokenBudget: number): Message[] {
|
|
101
|
+
if (tokenBudget <= 0 || messages.length === 0) return [];
|
|
102
|
+
|
|
103
|
+
// Walk backwards from newest, accumulating tokens
|
|
104
|
+
let budget = tokenBudget;
|
|
105
|
+
let startIndex = messages.length;
|
|
106
|
+
|
|
107
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
108
|
+
const msg = messages[i]!;
|
|
109
|
+
|
|
110
|
+
// Check if this is a tool_result — must include its tool_use pair
|
|
111
|
+
if (msg.role === "tool_result" && i > 0 && messages[i - 1]!.role === "tool_use") {
|
|
112
|
+
const pairCost = msg.tokenCount + messages[i - 1]!.tokenCount;
|
|
113
|
+
if (budget - pairCost < 0 && startIndex < messages.length) break;
|
|
114
|
+
budget -= pairCost;
|
|
115
|
+
startIndex = i - 1;
|
|
116
|
+
i--; // skip the tool_use we just included
|
|
117
|
+
} else if (
|
|
118
|
+
msg.role === "tool_use" &&
|
|
119
|
+
i < messages.length - 1 &&
|
|
120
|
+
messages[i + 1]!.role === "tool_result"
|
|
121
|
+
) {
|
|
122
|
+
} else {
|
|
123
|
+
if (budget - msg.tokenCount < 0 && startIndex < messages.length) break;
|
|
124
|
+
budget -= msg.tokenCount;
|
|
125
|
+
startIndex = i;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return messages.slice(startIndex);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
compact(tokenBudget: number, strategy: CompactionStrategy) {
|
|
133
|
+
const threshold = Math.floor(tokenBudget * 0.8);
|
|
134
|
+
if (runningTokens <= threshold) return;
|
|
135
|
+
|
|
136
|
+
if (strategy === "truncate" || strategy === "summarize") {
|
|
137
|
+
// summarize is treated as truncate in v1
|
|
138
|
+
// Drop oldest messages until under threshold, respecting atomic tool pairs
|
|
139
|
+
while (messages.length > 0 && runningTokens > threshold) {
|
|
140
|
+
const first = messages[0]!;
|
|
141
|
+
if (
|
|
142
|
+
first.role === "tool_use" &&
|
|
143
|
+
messages.length > 1 &&
|
|
144
|
+
messages[1]!.role === "tool_result"
|
|
145
|
+
) {
|
|
146
|
+
// Drop the pair together
|
|
147
|
+
runningTokens -= first.tokenCount + messages[1]!.tokenCount;
|
|
148
|
+
messages.splice(0, 2);
|
|
149
|
+
} else if (first.role === "tool_result") {
|
|
150
|
+
// Orphaned tool_result — drop it
|
|
151
|
+
runningTokens -= first.tokenCount;
|
|
152
|
+
messages.splice(0, 1);
|
|
153
|
+
} else {
|
|
154
|
+
runningTokens -= first.tokenCount;
|
|
155
|
+
messages.splice(0, 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else if (strategy === "sliding-window") {
|
|
159
|
+
// Keep newest messages that fit within threshold
|
|
160
|
+
let kept = 0;
|
|
161
|
+
let keepFrom = messages.length;
|
|
162
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
163
|
+
const msg = messages[i]!;
|
|
164
|
+
if (kept + msg.tokenCount > threshold) break;
|
|
165
|
+
// Ensure tool pairs stay together
|
|
166
|
+
if (msg.role === "tool_result" && i > 0 && messages[i - 1]!.role === "tool_use") {
|
|
167
|
+
const pairCost = msg.tokenCount + messages[i - 1]!.tokenCount;
|
|
168
|
+
if (kept + pairCost > threshold) break;
|
|
169
|
+
kept += pairCost;
|
|
170
|
+
keepFrom = i - 1;
|
|
171
|
+
i--; // skip tool_use
|
|
172
|
+
} else {
|
|
173
|
+
kept += msg.tokenCount;
|
|
174
|
+
keepFrom = i;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const removed = messages.splice(0, keepFrom);
|
|
178
|
+
runningTokens -= removed.reduce((s, m) => s + m.tokenCount, 0);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async save(storage: Storage) {
|
|
183
|
+
await storage.put(storageKey, JSON.stringify(messages));
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
async restore(storage: Storage) {
|
|
187
|
+
const data = await storage.get(storageKey);
|
|
188
|
+
if (data) {
|
|
189
|
+
messages = JSON.parse(data);
|
|
190
|
+
runningTokens = messages.reduce((sum, m) => sum + m.tokenCount, 0);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
totalTokens() {
|
|
195
|
+
return runningTokens;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|