@rowan-agent/agent 0.6.2 → 0.7.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/README.md +7 -9
- package/dist/index.d.ts +72 -19
- package/dist/index.js +411 -113
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -616,7 +616,7 @@ Two sources, merged by priority:
|
|
|
616
616
|
|
|
617
617
|
```yaml
|
|
618
618
|
---
|
|
619
|
-
name:
|
|
619
|
+
name: review
|
|
620
620
|
description: Review code for correctness and style
|
|
621
621
|
tools: [read, bash]
|
|
622
622
|
target: execute
|
|
@@ -625,15 +625,14 @@ target: execute
|
|
|
625
625
|
Review the current implementation for bugs and style issues.
|
|
626
626
|
```
|
|
627
627
|
|
|
628
|
-
**Extension-registered** — via `api.registerPhase()`.
|
|
628
|
+
**Extension-registered** — via `api.registerPhase()`. The phase name is used as its identity.
|
|
629
629
|
|
|
630
630
|
```ts
|
|
631
631
|
import type { ExtensionAPI } from "@rowan-agent/agent";
|
|
632
632
|
|
|
633
633
|
export default function myPlugin(api: ExtensionAPI) {
|
|
634
634
|
api.registerPhase({
|
|
635
|
-
|
|
636
|
-
name: "Code Review",
|
|
635
|
+
name: "review",
|
|
637
636
|
description: "Review code for correctness",
|
|
638
637
|
tools: ["read", "bash"],
|
|
639
638
|
async run(context, execution) {
|
|
@@ -648,8 +647,7 @@ export default function myPlugin(api: ExtensionAPI) {
|
|
|
648
647
|
|
|
649
648
|
```ts
|
|
650
649
|
interface Phase {
|
|
651
|
-
|
|
652
|
-
name: string;
|
|
650
|
+
name: string; // unique identity and display name
|
|
653
651
|
description: string;
|
|
654
652
|
tools?: string[]; // restrict tools (undefined = all)
|
|
655
653
|
skills?: string[]; // restrict skills
|
|
@@ -674,7 +672,7 @@ interface PhaseContext {
|
|
|
674
672
|
|
|
675
673
|
type PhaseOutput = {
|
|
676
674
|
message: string;
|
|
677
|
-
route: string; // "continue" | "stop" | <phase-
|
|
675
|
+
route: string; // "continue" | "stop" | <phase-name>
|
|
678
676
|
payload?: unknown; // data passed to the next phase
|
|
679
677
|
};
|
|
680
678
|
```
|
|
@@ -731,7 +729,7 @@ import type { ExtensionAPI } from "@rowan-agent/agent";
|
|
|
731
729
|
export default function myPlugin(rowan: ExtensionAPI) {
|
|
732
730
|
rowan.on("agent_start", (event) => { ... });
|
|
733
731
|
rowan.registerTool({ name: "my_tool", description: "...", parameters: {...}, execute: async (args) => {...} });
|
|
734
|
-
rowan.registerPhase({
|
|
732
|
+
rowan.registerPhase({ name: "review", description: "...", run: async (ctx) => {...} });
|
|
735
733
|
rowan.events.emit("my-plugin:ready", {});
|
|
736
734
|
}
|
|
737
735
|
```
|
|
@@ -775,7 +773,7 @@ type LoopMetrics = {
|
|
|
775
773
|
| `Skill` | Loaded skill bundle |
|
|
776
774
|
| `Phase` | Phase definition with content, execution, and routing config |
|
|
777
775
|
| `PhaseContext` / `PhaseOutput` | Phase input and output |
|
|
778
|
-
| `PhaseRegistry` | Map of phase
|
|
776
|
+
| `PhaseRegistry` | Map of phase names to Phase objects plus entry phase name |
|
|
779
777
|
| `Outcome` | Terminal result with message and tool results |
|
|
780
778
|
| `LoopMetrics` | Loop iteration, timing, and phase transition stats |
|
|
781
779
|
| `SessionManagerProvider` | Session lifecycle seam used by the Runtime |
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ type RuntimeEventId = OpaqueId<"RuntimeEventId">;
|
|
|
30
30
|
type LeaseId = OpaqueId<"LeaseId">;
|
|
31
31
|
type RuntimeToolCallId = OpaqueId<"RuntimeToolCallId">;
|
|
32
32
|
type AgentLifecycleState = "active" | "paused";
|
|
33
|
+
type SessionLifecycleState = "active" | "archived" | "deleting";
|
|
33
34
|
type RuntimeMessageState = "queued" | "leased" | "acknowledged" | "dead_lettered";
|
|
34
35
|
type AgentRunState = "queued" | "running" | "suspended" | "completed" | "failed" | "cancelled";
|
|
35
36
|
/** Opaque execution checkpoint owned by the Agent loop and persisted by Runtime. */
|
|
@@ -46,6 +47,8 @@ type AgentRecord = {
|
|
|
46
47
|
id: AgentId;
|
|
47
48
|
sessionId: string;
|
|
48
49
|
state: AgentLifecycleState;
|
|
50
|
+
lifecycleState: SessionLifecycleState;
|
|
51
|
+
parentSessionId?: string;
|
|
49
52
|
createdAt: string;
|
|
50
53
|
updatedAt: string;
|
|
51
54
|
};
|
|
@@ -84,7 +87,7 @@ type AgentRunRecord = {
|
|
|
84
87
|
createdAt: string;
|
|
85
88
|
updatedAt: string;
|
|
86
89
|
};
|
|
87
|
-
type RuntimeEventKind = "agent_created" | "agent_paused" | "agent_resumed" | "message_enqueued" | "run_enqueued" | "run_leased" | "lease_expired" | "lease_recovered" | "run_suspended" | "run_retry_scheduled" | "run_completed" | "run_aborted" | "message_acknowledged" | "message_dead_lettered" | "tool_call_created" | "tool_call_started" | "tool_call_failed" | "tool_call_completed" | "tool_call_indeterminate";
|
|
90
|
+
type RuntimeEventKind = "agent_created" | "agent_paused" | "agent_resumed" | "session_archived" | "session_unarchived" | "message_enqueued" | "run_enqueued" | "run_leased" | "lease_expired" | "lease_recovered" | "run_suspended" | "run_retry_scheduled" | "run_completed" | "run_aborted" | "message_acknowledged" | "message_dead_lettered" | "tool_call_created" | "tool_call_started" | "tool_call_failed" | "tool_call_completed" | "tool_call_indeterminate";
|
|
88
91
|
type RuntimeEvent = {
|
|
89
92
|
id: RuntimeEventId;
|
|
90
93
|
sequence: number;
|
|
@@ -325,9 +328,7 @@ interface PhaseContext {
|
|
|
325
328
|
* Loaded Phase object
|
|
326
329
|
*/
|
|
327
330
|
interface Phase {
|
|
328
|
-
/** Unique
|
|
329
|
-
id: string;
|
|
330
|
-
/** Display name */
|
|
331
|
+
/** Unique phase identity and display name. */
|
|
331
332
|
name: string;
|
|
332
333
|
/** Description */
|
|
333
334
|
description: string;
|
|
@@ -358,9 +359,9 @@ interface Phase {
|
|
|
358
359
|
* Phase registry containing all loaded phases
|
|
359
360
|
*/
|
|
360
361
|
interface PhaseRegistry {
|
|
361
|
-
/** Map of phase
|
|
362
|
+
/** Map of phase name to Phase object */
|
|
362
363
|
phases: Map<string, Phase>;
|
|
363
|
-
/** Entry phase
|
|
364
|
+
/** Entry phase name (null until Agent applies its default phase) */
|
|
364
365
|
entryPhaseId: string | null;
|
|
365
366
|
}
|
|
366
367
|
|
|
@@ -372,7 +373,6 @@ interface PhaseRegistry {
|
|
|
372
373
|
type PhaseRun = (context: PhaseContext, execution: PhaseExecution) => Promise<PhaseOutput | void>;
|
|
373
374
|
/** Phase definition shape used by extensions */
|
|
374
375
|
type PhaseDefinition = {
|
|
375
|
-
id: string;
|
|
376
376
|
name: string;
|
|
377
377
|
description: string;
|
|
378
378
|
run?: PhaseRun;
|
|
@@ -382,7 +382,7 @@ type PhaseDefinition = {
|
|
|
382
382
|
input?: Record<string, string>;
|
|
383
383
|
model?: string;
|
|
384
384
|
};
|
|
385
|
-
type PhaseRegistration =
|
|
385
|
+
type PhaseRegistration = Omit<PhaseDefinition, 'run'> & {
|
|
386
386
|
/** Optional execution override — takes over model invocation */
|
|
387
387
|
run?: PhaseRun;
|
|
388
388
|
};
|
|
@@ -480,7 +480,6 @@ interface ExtensionManifest {
|
|
|
480
480
|
entry?: string;
|
|
481
481
|
name?: string;
|
|
482
482
|
phase?: {
|
|
483
|
-
id?: string;
|
|
484
483
|
name?: string;
|
|
485
484
|
description?: string;
|
|
486
485
|
tools?: string[];
|
|
@@ -603,7 +602,7 @@ interface EventBus {
|
|
|
603
602
|
*/
|
|
604
603
|
interface BeforePhaseEvent {
|
|
605
604
|
type: "before_phase";
|
|
606
|
-
/** Phase
|
|
605
|
+
/** Phase name about to execute */
|
|
607
606
|
phaseId: string;
|
|
608
607
|
/** Phase input */
|
|
609
608
|
input: PhaseContext;
|
|
@@ -617,7 +616,7 @@ interface BeforePhaseEvent {
|
|
|
617
616
|
*/
|
|
618
617
|
interface AfterPhaseEvent {
|
|
619
618
|
type: "after_phase";
|
|
620
|
-
/** Phase
|
|
619
|
+
/** Phase name that completed */
|
|
621
620
|
phaseId: string;
|
|
622
621
|
/** Phase output */
|
|
623
622
|
output: PhaseOutput;
|
|
@@ -629,7 +628,7 @@ interface AfterPhaseEvent {
|
|
|
629
628
|
*/
|
|
630
629
|
interface BeforePromptEvent {
|
|
631
630
|
type: "before_prompt";
|
|
632
|
-
/** Current phase
|
|
631
|
+
/** Current phase name */
|
|
633
632
|
phaseId: string;
|
|
634
633
|
/** Phase input (modifiable) */
|
|
635
634
|
input: PhaseContext;
|
|
@@ -961,10 +960,10 @@ interface ExtensionContext {
|
|
|
961
960
|
}>;
|
|
962
961
|
/** Get skill content by name */
|
|
963
962
|
getSkillContent?(skillName: string): string;
|
|
964
|
-
/** Get all available phase
|
|
963
|
+
/** Get all available phase names */
|
|
965
964
|
getAvailablePhases?(): string[];
|
|
966
|
-
/** Get phase content by
|
|
967
|
-
getPhaseContent?(
|
|
965
|
+
/** Get phase content by name */
|
|
966
|
+
getPhaseContent?(phaseName: string): string;
|
|
968
967
|
}
|
|
969
968
|
/**
|
|
970
969
|
* Utility functions available to extensions.
|
|
@@ -1033,10 +1032,10 @@ interface ExtensionAPI {
|
|
|
1033
1032
|
setPayload(payload: unknown): void;
|
|
1034
1033
|
/** Phase Out: set outcome message */
|
|
1035
1034
|
setMessage(message: string): void;
|
|
1036
|
-
/** Get current phase
|
|
1035
|
+
/** Get current phase name */
|
|
1037
1036
|
getCurrentPhase(): string;
|
|
1038
1037
|
/** Set next phase (lower priority than PHASE.md target) */
|
|
1039
|
-
setNextPhase(
|
|
1038
|
+
setNextPhase(phaseName: string): void;
|
|
1040
1039
|
/** Get the next phase set by setNextPhase */
|
|
1041
1040
|
getNextPhase(): string | undefined;
|
|
1042
1041
|
/** Get the message set by setMessage */
|
|
@@ -1055,7 +1054,7 @@ type ExtensionFactory = (api: ExtensionAPI) => void | Promise<void>;
|
|
|
1055
1054
|
* Scans for subdirectories containing PHASE.md files.
|
|
1056
1055
|
* Returns PhaseRegistry with entryPhaseId:
|
|
1057
1056
|
* - null by default (caller must explicitly set to start from a specific phase)
|
|
1058
|
-
* - Set to a specific phase
|
|
1057
|
+
* - Set to a specific phase name to start from that phase
|
|
1059
1058
|
*
|
|
1060
1059
|
* When entryPhaseId is null, Agent normalizes the registry to start from its default phase.
|
|
1061
1060
|
*/
|
|
@@ -1212,6 +1211,19 @@ declare const SkillSchema: Type.TObject<{
|
|
|
1212
1211
|
}>;
|
|
1213
1212
|
type Skill = Type.Static<typeof SkillSchema>;
|
|
1214
1213
|
|
|
1214
|
+
type SessionHeader = {
|
|
1215
|
+
type: "header";
|
|
1216
|
+
id: string;
|
|
1217
|
+
version: string;
|
|
1218
|
+
createdAt: string;
|
|
1219
|
+
updatedAt: string;
|
|
1220
|
+
systemPrompt: string;
|
|
1221
|
+
input: string;
|
|
1222
|
+
parentSessionId?: string;
|
|
1223
|
+
skills: Skill[];
|
|
1224
|
+
title?: string;
|
|
1225
|
+
currentLeafId?: string | null;
|
|
1226
|
+
};
|
|
1215
1227
|
type SessionAgentContext<TTool = unknown> = {
|
|
1216
1228
|
systemPrompt: string;
|
|
1217
1229
|
messages: AgentMessage[];
|
|
@@ -1241,6 +1253,7 @@ type SessionListItem = {
|
|
|
1241
1253
|
};
|
|
1242
1254
|
type SessionManager = {
|
|
1243
1255
|
getSessionId(): string;
|
|
1256
|
+
getHeader(): Promise<SessionHeader>;
|
|
1244
1257
|
appendMessage(message: AgentMessage): Promise<string>;
|
|
1245
1258
|
appendOutcome(outcome: Outcome): Promise<string>;
|
|
1246
1259
|
appendModelTranscript(transcript: ModelTranscript, meta?: {
|
|
@@ -1252,10 +1265,13 @@ type SessionManager = {
|
|
|
1252
1265
|
type SessionManagerProvider = {
|
|
1253
1266
|
create(input: CreateSessionManagerInput): Promise<SessionManager>;
|
|
1254
1267
|
open(sessionId: string): Promise<SessionManager | undefined>;
|
|
1268
|
+
list(): Promise<SessionListItem[]>;
|
|
1269
|
+
delete(sessionId: string): Promise<boolean>;
|
|
1255
1270
|
};
|
|
1256
1271
|
|
|
1257
1272
|
type CreateAgentInput = {
|
|
1258
1273
|
sessionId: string;
|
|
1274
|
+
parentSessionId?: string;
|
|
1259
1275
|
};
|
|
1260
1276
|
type EnqueueAgentInput = {
|
|
1261
1277
|
agentId: AgentId;
|
|
@@ -1344,6 +1360,9 @@ interface RuntimeStateStore {
|
|
|
1344
1360
|
getAgent(agentId: AgentId): Promise<AgentRecord | undefined>;
|
|
1345
1361
|
listAgents(): Promise<AgentRecord[]>;
|
|
1346
1362
|
setAgentState(agentId: AgentId, state: AgentLifecycleState): Promise<AgentRecord>;
|
|
1363
|
+
setAgentLifecycleState(agentId: AgentId, state: SessionLifecycleState): Promise<AgentRecord>;
|
|
1364
|
+
getAgentBySessionId(sessionId: string): Promise<AgentRecord | undefined>;
|
|
1365
|
+
deleteAgentData(agentId: AgentId): Promise<void>;
|
|
1347
1366
|
enqueueAgentInput(input: EnqueueAgentInput): Promise<EnqueuedAgentInput>;
|
|
1348
1367
|
getMessage(messageId: RuntimeMessageId): Promise<RuntimeMessage | undefined>;
|
|
1349
1368
|
getRun(runId: AgentRunId): Promise<AgentRunRecord | undefined>;
|
|
@@ -1399,6 +1418,17 @@ type RuntimeEventConsumer = {
|
|
|
1399
1418
|
caughtUp: Promise<void>;
|
|
1400
1419
|
stop(): void;
|
|
1401
1420
|
};
|
|
1421
|
+
type RuntimeSessionSummary = {
|
|
1422
|
+
id: string;
|
|
1423
|
+
state: SessionLifecycleState;
|
|
1424
|
+
createdAt: string;
|
|
1425
|
+
updatedAt: string;
|
|
1426
|
+
archivedAt?: string;
|
|
1427
|
+
parentSessionId?: string;
|
|
1428
|
+
agentId: AgentId;
|
|
1429
|
+
activeRunCount: number;
|
|
1430
|
+
title?: string;
|
|
1431
|
+
};
|
|
1402
1432
|
declare class AgentRuntime {
|
|
1403
1433
|
private readonly stateStore;
|
|
1404
1434
|
private readonly sessionProvider?;
|
|
@@ -1409,6 +1439,7 @@ declare class AgentRuntime {
|
|
|
1409
1439
|
private readonly toolRuntime;
|
|
1410
1440
|
private readonly bindings;
|
|
1411
1441
|
private readonly agents;
|
|
1442
|
+
private readonly invalidatedAgents;
|
|
1412
1443
|
private readonly runHandles;
|
|
1413
1444
|
private readonly runWaiters;
|
|
1414
1445
|
private readonly pendingRuns;
|
|
@@ -1436,6 +1467,13 @@ declare class AgentRuntime {
|
|
|
1436
1467
|
getRun(runId: AgentRunId): Promise<AgentRunRecord | undefined>;
|
|
1437
1468
|
listActiveRuns(): Promise<AgentRunRecord[]>;
|
|
1438
1469
|
listRuns(input?: ListRunsInput): Promise<AgentRunRecord[]>;
|
|
1470
|
+
listSessions(input?: {
|
|
1471
|
+
state?: SessionLifecycleState;
|
|
1472
|
+
}): Promise<RuntimeSessionSummary[]>;
|
|
1473
|
+
archiveSession(sessionId: string): Promise<void>;
|
|
1474
|
+
unarchiveSession(sessionId: string): Promise<void>;
|
|
1475
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
1476
|
+
private requireSessionAgent;
|
|
1439
1477
|
getAgent(agentId: AgentId): Agent | undefined;
|
|
1440
1478
|
getMessage(messageId: RuntimeMessageId): Promise<RuntimeMessage | undefined>;
|
|
1441
1479
|
getToolCall(toolCallId: RuntimeToolCallId): Promise<RuntimeToolCall | undefined>;
|
|
@@ -1537,6 +1575,14 @@ declare class InMemorySessionStore implements SessionManagerProvider {
|
|
|
1537
1575
|
private readonly sessions;
|
|
1538
1576
|
create(input: CreateSessionManagerInput): Promise<SessionManager>;
|
|
1539
1577
|
open(sessionId: string): Promise<SessionManager | undefined>;
|
|
1578
|
+
list(): Promise<{
|
|
1579
|
+
id: string;
|
|
1580
|
+
title: string | undefined;
|
|
1581
|
+
createdAt: string;
|
|
1582
|
+
updatedAt: string;
|
|
1583
|
+
messageCount: any;
|
|
1584
|
+
}[]>;
|
|
1585
|
+
delete(sessionId: string): Promise<boolean>;
|
|
1540
1586
|
}
|
|
1541
1587
|
declare class JsonlSessionStore implements SessionManagerProvider {
|
|
1542
1588
|
private readonly sessionsDir;
|
|
@@ -1544,6 +1590,7 @@ declare class JsonlSessionStore implements SessionManagerProvider {
|
|
|
1544
1590
|
create(input: CreateSessionManagerInput): Promise<SessionManager>;
|
|
1545
1591
|
open(sessionId: string): Promise<SessionManager | undefined>;
|
|
1546
1592
|
list(): Promise<SessionListItem[]>;
|
|
1593
|
+
delete(sessionId: string): Promise<boolean>;
|
|
1547
1594
|
}
|
|
1548
1595
|
|
|
1549
1596
|
type CoreToolContext = {
|
|
@@ -1563,8 +1610,11 @@ declare class InMemoryRuntimeStateStore implements RuntimeStateStore {
|
|
|
1563
1610
|
private readonly eventCheckpoints;
|
|
1564
1611
|
createAgent(input: CreateAgentInput): Promise<AgentRecord>;
|
|
1565
1612
|
getAgent(agentId: AgentId): Promise<AgentRecord | undefined>;
|
|
1613
|
+
getAgentBySessionId(sessionId: string): Promise<AgentRecord | undefined>;
|
|
1566
1614
|
listAgents(): Promise<AgentRecord[]>;
|
|
1567
1615
|
setAgentState(agentId: AgentId, state: AgentRecord["state"]): Promise<AgentRecord>;
|
|
1616
|
+
setAgentLifecycleState(agentId: AgentId, state: AgentRecord["lifecycleState"]): Promise<AgentRecord>;
|
|
1617
|
+
deleteAgentData(agentId: AgentId): Promise<void>;
|
|
1568
1618
|
enqueueAgentInput(input: EnqueueAgentInput): Promise<EnqueuedAgentInput>;
|
|
1569
1619
|
private enqueueAgentInputState;
|
|
1570
1620
|
getMessage(messageId: RuntimeMessageId): Promise<RuntimeMessage | undefined>;
|
|
@@ -1603,8 +1653,11 @@ declare class SqliteRuntimeStateStore implements RuntimeStateStore {
|
|
|
1603
1653
|
close(): void;
|
|
1604
1654
|
createAgent(input: CreateAgentInput): Promise<AgentRecord>;
|
|
1605
1655
|
getAgent(agentId: AgentId): Promise<AgentRecord | undefined>;
|
|
1656
|
+
getAgentBySessionId(sessionId: string): Promise<AgentRecord | undefined>;
|
|
1606
1657
|
listAgents(): Promise<AgentRecord[]>;
|
|
1607
1658
|
setAgentState(agentId: AgentId, state: AgentRecord["state"]): Promise<AgentRecord>;
|
|
1659
|
+
setAgentLifecycleState(agentId: AgentId, state: AgentRecord["lifecycleState"]): Promise<AgentRecord>;
|
|
1660
|
+
deleteAgentData(agentId: AgentId): Promise<void>;
|
|
1608
1661
|
enqueueAgentInput(input: EnqueueAgentInput): Promise<EnqueuedAgentInput>;
|
|
1609
1662
|
private enqueueAgentInputState;
|
|
1610
1663
|
getMessage(messageId: RuntimeMessageId): Promise<RuntimeMessage | undefined>;
|
|
@@ -1648,4 +1701,4 @@ declare class SqliteRuntimeStateStore implements RuntimeStateStore {
|
|
|
1648
1701
|
private recordEventInternal;
|
|
1649
1702
|
}
|
|
1650
1703
|
|
|
1651
|
-
export { type AfterToolCall, Agent, type AgentContext, type AgentId, type AgentInputRequest, type AgentOptions, AgentRun, type AgentRunExecutionState, type AgentRunId, type AgentRunListener, type AgentRunMetadata, type AgentRunRecord, type AgentRunState, AgentRuntime, type AgentRuntimeOptions, type BeforeToolCall, type CoreToolContext, type ExecutionTurn, type ExtensionAPI, type ExtensionFactory, type HookEvent, type HookEventType, type HookHandler, InMemoryRuntimeStateStore, InMemorySessionStore, JsonlSessionStore, type LoadExtensionsResult, type LoadedExtension, type LoopMetrics, type ModelTranscript, type Phase, type PhaseContext, type PhaseExecution, type PhaseInvocation, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseState, type RuntimeEvent, type RuntimeEventConsumer, type RuntimeEventCursor, type RuntimeEventDisposition, type RuntimeEventKind, type RuntimeEventListener, type RuntimeMessage, type RuntimeMessageId, type RuntimeToolCall, type RuntimeToolCallId, type SessionManagerProvider, SqliteRuntimeStateStore, type Tool, type ToolContext, type ToolDefinition, type ToolExecutionMode, type ToolExecutionResult, type ToolRuntimePolicy, type Unsubscribe, createCoreTools, createMessage };
|
|
1704
|
+
export { type AfterToolCall, Agent, type AgentContext, type AgentId, type AgentInputRequest, type AgentOptions, AgentRun, type AgentRunExecutionState, type AgentRunId, type AgentRunListener, type AgentRunMetadata, type AgentRunRecord, type AgentRunState, AgentRuntime, type AgentRuntimeOptions, type BeforeToolCall, type CoreToolContext, type ExecutionTurn, type ExtensionAPI, type ExtensionFactory, type HookEvent, type HookEventType, type HookHandler, InMemoryRuntimeStateStore, InMemorySessionStore, JsonlSessionStore, type LoadExtensionsResult, type LoadedExtension, type LoopMetrics, type ModelTranscript, type Phase, type PhaseContext, type PhaseExecution, type PhaseInvocation, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseState, type RuntimeEvent, type RuntimeEventConsumer, type RuntimeEventCursor, type RuntimeEventDisposition, type RuntimeEventKind, type RuntimeEventListener, type RuntimeMessage, type RuntimeMessageId, type RuntimeSessionSummary, type RuntimeToolCall, type RuntimeToolCallId, type SessionLifecycleState, type SessionManagerProvider, SqliteRuntimeStateStore, type Tool, type ToolContext, type ToolDefinition, type ToolExecutionMode, type ToolExecutionResult, type ToolRuntimePolicy, type Unsubscribe, createCoreTools, createMessage };
|
package/dist/index.js
CHANGED
|
@@ -387,51 +387,29 @@ import { dirname as dirname2, join, resolve } from "path";
|
|
|
387
387
|
// src/harness/loader.ts
|
|
388
388
|
import { readFile } from "fs/promises";
|
|
389
389
|
import { basename, dirname, extname } from "path";
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
for (let i = 0; i < lines.length; i++) {
|
|
396
|
-
const line = lines[i];
|
|
397
|
-
const idx = line.indexOf(":");
|
|
398
|
-
if (idx === -1) continue;
|
|
399
|
-
const key = line.slice(0, idx).trim();
|
|
400
|
-
const value = line.slice(idx + 1).trim();
|
|
401
|
-
if (!key) continue;
|
|
402
|
-
if (value === "" && i + 1 < lines.length) {
|
|
403
|
-
const nested = {};
|
|
404
|
-
while (i + 1 < lines.length) {
|
|
405
|
-
const next = lines[i + 1];
|
|
406
|
-
if (!next.startsWith(" ") || next.trim() === "") break;
|
|
407
|
-
const nIdx = next.indexOf(":");
|
|
408
|
-
if (nIdx === -1) break;
|
|
409
|
-
const nKey = next.slice(0, nIdx).trim();
|
|
410
|
-
const nValue = next.slice(nIdx + 1).trim();
|
|
411
|
-
nested[nKey] = nValue;
|
|
412
|
-
i++;
|
|
413
|
-
}
|
|
414
|
-
if (Object.keys(nested).length) {
|
|
415
|
-
frontmatter[key] = nested;
|
|
416
|
-
}
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
frontmatter[key] = parseValue(value);
|
|
420
|
-
}
|
|
421
|
-
return { frontmatter, body: raw.slice(match[0].length) };
|
|
422
|
-
}
|
|
423
|
-
function parseValue(value) {
|
|
424
|
-
const arrayMatch = value.match(/^\[(.*)\]$/);
|
|
425
|
-
if (arrayMatch) {
|
|
426
|
-
return arrayMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
390
|
+
import { parse as parseYaml } from "yaml";
|
|
391
|
+
var FrontmatterParseError = class extends Error {
|
|
392
|
+
constructor(message) {
|
|
393
|
+
super(message);
|
|
394
|
+
this.name = "FrontmatterParseError";
|
|
427
395
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
396
|
+
};
|
|
397
|
+
function parseFrontmatter(raw) {
|
|
398
|
+
const normalized = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
399
|
+
if (!normalized.startsWith("---")) return { frontmatter: {}, body: normalized };
|
|
400
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
401
|
+
if (endIndex === -1) return { frontmatter: {}, body: normalized };
|
|
402
|
+
const yamlString = normalized.slice(4, endIndex);
|
|
403
|
+
try {
|
|
404
|
+
const frontmatter = parseYaml(yamlString) ?? {};
|
|
405
|
+
return {
|
|
406
|
+
frontmatter,
|
|
407
|
+
body: normalized.slice(endIndex + 4).trim()
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
411
|
+
throw new FrontmatterParseError(message);
|
|
433
412
|
}
|
|
434
|
-
return value;
|
|
435
413
|
}
|
|
436
414
|
async function loadMarkdown(filePath) {
|
|
437
415
|
const raw = await readFile(filePath, "utf8");
|
|
@@ -543,6 +521,82 @@ function buildPhaseDirectiveMessage(phase, output) {
|
|
|
543
521
|
return parts.join("\n");
|
|
544
522
|
}
|
|
545
523
|
|
|
524
|
+
// src/harness/resource-validation.ts
|
|
525
|
+
var MAX_RESOURCE_ID_LENGTH = 64;
|
|
526
|
+
var MAX_DESCRIPTION_LENGTH = 1024;
|
|
527
|
+
var ResourceMetadataError = class extends Error {
|
|
528
|
+
constructor(code, message) {
|
|
529
|
+
super(message);
|
|
530
|
+
this.code = code;
|
|
531
|
+
this.name = "ResourceMetadataError";
|
|
532
|
+
}
|
|
533
|
+
code;
|
|
534
|
+
};
|
|
535
|
+
function validateResourceId(id, label = "id") {
|
|
536
|
+
const errors = [];
|
|
537
|
+
if (id.length > MAX_RESOURCE_ID_LENGTH) {
|
|
538
|
+
errors.push(`${label} exceeds ${MAX_RESOURCE_ID_LENGTH} characters (${id.length})`);
|
|
539
|
+
}
|
|
540
|
+
if (!/^[a-z0-9-]+$/.test(id)) {
|
|
541
|
+
errors.push(`${label} contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);
|
|
542
|
+
}
|
|
543
|
+
if (id.startsWith("-") || id.endsWith("-")) {
|
|
544
|
+
errors.push(`${label} must not start or end with a hyphen`);
|
|
545
|
+
}
|
|
546
|
+
if (id.includes("--")) {
|
|
547
|
+
errors.push(`${label} must not contain consecutive hyphens`);
|
|
548
|
+
}
|
|
549
|
+
return errors;
|
|
550
|
+
}
|
|
551
|
+
function validateResourceName(name, id) {
|
|
552
|
+
const errors = [];
|
|
553
|
+
if (name !== id) {
|
|
554
|
+
errors.push(`name "${name}" does not match parent directory "${id}"`);
|
|
555
|
+
errors.push(...validateResourceId(name, "name"));
|
|
556
|
+
}
|
|
557
|
+
return errors;
|
|
558
|
+
}
|
|
559
|
+
function validateDescription(value) {
|
|
560
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
561
|
+
return {
|
|
562
|
+
warnings: ["description is required"],
|
|
563
|
+
missing: true
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (value.length > MAX_DESCRIPTION_LENGTH) {
|
|
567
|
+
return {
|
|
568
|
+
description: value,
|
|
569
|
+
warnings: [`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${value.length})`],
|
|
570
|
+
missing: false
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
return { description: value, warnings: [], missing: false };
|
|
574
|
+
}
|
|
575
|
+
function validateSkillReferences(value) {
|
|
576
|
+
if (value === void 0) return [];
|
|
577
|
+
if (!Array.isArray(value)) return ["skills must be an array"];
|
|
578
|
+
const errors = [];
|
|
579
|
+
for (const [index, skillId] of value.entries()) {
|
|
580
|
+
if (typeof skillId !== "string") {
|
|
581
|
+
errors.push(`skills[${index}] must be a string`);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
errors.push(...validateResourceId(skillId, `skills[${index}]`));
|
|
585
|
+
}
|
|
586
|
+
return errors;
|
|
587
|
+
}
|
|
588
|
+
function validatePhaseTarget(value) {
|
|
589
|
+
if (value === void 0) return [];
|
|
590
|
+
if (typeof value !== "string") return ["target must be a string"];
|
|
591
|
+
if (value === "stop") return [];
|
|
592
|
+
return validateResourceId(value, "target");
|
|
593
|
+
}
|
|
594
|
+
function warnResourceDiagnostics(kind, filePath, diagnostics) {
|
|
595
|
+
for (const diagnostic of diagnostics) {
|
|
596
|
+
console.warn(`Invalid ${kind} metadata at "${filePath}": ${diagnostic}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
546
600
|
// src/harness/phases/loader.ts
|
|
547
601
|
var PHASE_MARKER = "PHASE.md";
|
|
548
602
|
function resolvePhasePath(input) {
|
|
@@ -551,28 +605,44 @@ function resolvePhasePath(input) {
|
|
|
551
605
|
}
|
|
552
606
|
async function loadPhase(targetPath) {
|
|
553
607
|
const resolved = resolvePhasePath(targetPath);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
throw new
|
|
608
|
+
let loaded;
|
|
609
|
+
try {
|
|
610
|
+
loaded = await loadMarkdown(resolved);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (!(error instanceof FrontmatterParseError)) throw error;
|
|
613
|
+
warnResourceDiagnostics("phase", resolved, [`frontmatter could not be parsed: ${error.message}`]);
|
|
614
|
+
throw new ResourceMetadataError("parse_failed", error.message);
|
|
615
|
+
}
|
|
616
|
+
const directoryName = inferResourceName(resolved, PHASE_MARKER);
|
|
617
|
+
const { frontmatter, body } = loaded;
|
|
618
|
+
const metadata = frontmatter;
|
|
619
|
+
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
620
|
+
const name = frontmatterName || directoryName;
|
|
621
|
+
const description = validateDescription(metadata.description);
|
|
622
|
+
const diagnostics = validateResourceId(directoryName);
|
|
623
|
+
if (frontmatterName && frontmatterName !== directoryName) {
|
|
624
|
+
diagnostics.push(...validateResourceName(frontmatterName, directoryName));
|
|
625
|
+
}
|
|
626
|
+
diagnostics.push(...description.warnings);
|
|
627
|
+
diagnostics.push(...validateSkillReferences(metadata.skills));
|
|
628
|
+
diagnostics.push(...validatePhaseTarget(metadata.target));
|
|
629
|
+
warnResourceDiagnostics("phase", resolved, diagnostics);
|
|
630
|
+
if (description.missing) {
|
|
631
|
+
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
561
632
|
}
|
|
562
633
|
const baseDir = dirname2(resolved);
|
|
563
634
|
const phase = {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
isolated: frontmatter.isolated,
|
|
635
|
+
name,
|
|
636
|
+
description: description.description,
|
|
637
|
+
tools: metadata.tools,
|
|
638
|
+
skills: metadata.skills,
|
|
639
|
+
target: metadata.target,
|
|
640
|
+
input: metadata.input,
|
|
641
|
+
isolated: metadata.isolated,
|
|
572
642
|
filePath: resolved,
|
|
573
643
|
baseDir,
|
|
574
644
|
content: body,
|
|
575
|
-
model: parseModelRef(
|
|
645
|
+
model: parseModelRef(metadata.model)
|
|
576
646
|
};
|
|
577
647
|
const codePath = await discoverPhaseCode(baseDir);
|
|
578
648
|
if (codePath) {
|
|
@@ -598,13 +668,21 @@ async function loadPhases(targetPath) {
|
|
|
598
668
|
const phases = /* @__PURE__ */ new Map();
|
|
599
669
|
const phasesDir = resolve(targetPath);
|
|
600
670
|
if (existsSync(phasesDir) && statSync(phasesDir).isFile()) {
|
|
601
|
-
|
|
602
|
-
|
|
671
|
+
try {
|
|
672
|
+
const phase = await loadPhase(phasesDir);
|
|
673
|
+
phases.set(phase.name, phase);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (!(error instanceof ResourceMetadataError)) throw error;
|
|
676
|
+
}
|
|
603
677
|
return { phases, entryPhaseId: null };
|
|
604
678
|
}
|
|
605
679
|
if (existsSync(join(phasesDir, PHASE_MARKER))) {
|
|
606
|
-
|
|
607
|
-
|
|
680
|
+
try {
|
|
681
|
+
const phase = await loadPhase(phasesDir);
|
|
682
|
+
phases.set(phase.name, phase);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (!(error instanceof ResourceMetadataError)) throw error;
|
|
685
|
+
}
|
|
608
686
|
return { phases, entryPhaseId: null };
|
|
609
687
|
}
|
|
610
688
|
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
@@ -614,8 +692,9 @@ async function loadPhases(targetPath) {
|
|
|
614
692
|
if (!existsSync(phaseFile)) continue;
|
|
615
693
|
try {
|
|
616
694
|
const phase = await loadPhase(phaseFile);
|
|
617
|
-
phases.set(phase.
|
|
695
|
+
phases.set(phase.name, phase);
|
|
618
696
|
} catch (error) {
|
|
697
|
+
if (error instanceof ResourceMetadataError) continue;
|
|
619
698
|
console.warn(`Failed to load phase "${entry.name}":`, error);
|
|
620
699
|
}
|
|
621
700
|
}
|
|
@@ -653,8 +732,7 @@ async function loadPhaseCode(codePath) {
|
|
|
653
732
|
var DEFAULT_PHASE_ID = "default";
|
|
654
733
|
function createDefaultPhase() {
|
|
655
734
|
return {
|
|
656
|
-
|
|
657
|
-
name: "Execution Phase",
|
|
735
|
+
name: DEFAULT_PHASE_ID,
|
|
658
736
|
description: "Executes concrete task operations and produces artifacts.",
|
|
659
737
|
filePath: "",
|
|
660
738
|
baseDir: "",
|
|
@@ -678,7 +756,7 @@ function normalizeRelativePath(path) {
|
|
|
678
756
|
import Type from "typebox";
|
|
679
757
|
var PhaseRouteTool = "route";
|
|
680
758
|
function buildPhaseEntry(p) {
|
|
681
|
-
const entry = {
|
|
759
|
+
const entry = { name: p.name, description: p.description };
|
|
682
760
|
if (p.tools && p.tools.length > 0) {
|
|
683
761
|
entry.available_tools = p.tools.join(", ");
|
|
684
762
|
}
|
|
@@ -693,7 +771,7 @@ function buildPhaseEntry(p) {
|
|
|
693
771
|
function buildRouteDescription(availablePhases) {
|
|
694
772
|
const phasesBlock = buildStructuredSection("phase", [
|
|
695
773
|
...availablePhases.map(buildPhaseEntry),
|
|
696
|
-
{
|
|
774
|
+
{ name: "stop", description: "Terminate the workflow and return final result to the user" }
|
|
697
775
|
]);
|
|
698
776
|
return [
|
|
699
777
|
"Route execution to one or more phases concurrently, or stop execution.",
|
|
@@ -714,7 +792,7 @@ function buildRouteDescription(availablePhases) {
|
|
|
714
792
|
function createRouteTool(availablePhases) {
|
|
715
793
|
const DecisionTarget = Type.Object({
|
|
716
794
|
phase: Type.Union([
|
|
717
|
-
...availablePhases.map((p) => Type.Literal(p.
|
|
795
|
+
...availablePhases.map((p) => Type.Literal(p.name)),
|
|
718
796
|
Type.Literal("stop")
|
|
719
797
|
]),
|
|
720
798
|
reason: Type.Optional(Type.String({ description: "Brief reason for this decision" })),
|
|
@@ -1444,12 +1522,12 @@ function injectPhaseContent(phase, output, messages, mirror) {
|
|
|
1444
1522
|
try {
|
|
1445
1523
|
const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
|
|
1446
1524
|
const content = buildPhaseDirectiveMessage(
|
|
1447
|
-
{ name: phase.
|
|
1525
|
+
{ name: phase.name, content: phaseContent },
|
|
1448
1526
|
output
|
|
1449
1527
|
);
|
|
1450
1528
|
const message = createMessage("user", content, {
|
|
1451
1529
|
kind: "phase_prompt",
|
|
1452
|
-
phase: phase.
|
|
1530
|
+
phase: phase.name
|
|
1453
1531
|
});
|
|
1454
1532
|
messages.push(message);
|
|
1455
1533
|
if (mirror && mirror !== messages) mirror.push(message);
|
|
@@ -1594,7 +1672,7 @@ async function executePhaseWithModel(ctx) {
|
|
|
1594
1672
|
for (const toolCall of collected.toolCalls) {
|
|
1595
1673
|
if (toolCall.name !== PhaseRouteTool) continue;
|
|
1596
1674
|
const messageId = ctx.messageManager.start("tool", createRouteToolResultContent(toolCall), {
|
|
1597
|
-
phase: ctx.phase.
|
|
1675
|
+
phase: ctx.phase.name
|
|
1598
1676
|
});
|
|
1599
1677
|
await ctx.messageManager.end(messageId);
|
|
1600
1678
|
}
|
|
@@ -1607,7 +1685,7 @@ async function executePhaseWithModel(ctx) {
|
|
|
1607
1685
|
for (const toolCall of executableToolCalls) {
|
|
1608
1686
|
const result = await ctx.execution.executeTool(roundContext, toolCall);
|
|
1609
1687
|
const messageId = ctx.messageManager.start("tool", createToolResultContent(result), {
|
|
1610
|
-
phase: ctx.phase.
|
|
1688
|
+
phase: ctx.phase.name
|
|
1611
1689
|
});
|
|
1612
1690
|
await ctx.messageManager.end(messageId);
|
|
1613
1691
|
}
|
|
@@ -1646,7 +1724,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1646
1724
|
while (currentPhaseId) {
|
|
1647
1725
|
const availablePhases = [];
|
|
1648
1726
|
for (const [, phase2] of registry.phases) {
|
|
1649
|
-
availablePhases.push({
|
|
1727
|
+
availablePhases.push({ name: phase2.name, description: phase2.description, tools: phase2.tools, skills: phase2.skills, input: phase2.input, isolated: phase2.isolated });
|
|
1650
1728
|
}
|
|
1651
1729
|
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
1652
1730
|
if (abortResult.stopReason !== "none") {
|
|
@@ -1759,7 +1837,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1759
1837
|
} else if (latestUserInputMessage) {
|
|
1760
1838
|
const phaseInputMessage = createMessage("user", latestUserInputMessage.content, {
|
|
1761
1839
|
kind: "phase_input",
|
|
1762
|
-
phase: phase.
|
|
1840
|
+
phase: phase.name
|
|
1763
1841
|
});
|
|
1764
1842
|
config.context.messages.push(phaseInputMessage);
|
|
1765
1843
|
phaseContext.messages.push(phaseInputMessage);
|
|
@@ -1931,7 +2009,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1931
2009
|
ts: createTimestamp()
|
|
1932
2010
|
});
|
|
1933
2011
|
previousPayload = output.payload;
|
|
1934
|
-
previousResults = output.payload !== void 0 ? [{ name: phase.
|
|
2012
|
+
previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
|
|
1935
2013
|
currentPhaseId = targetPhaseId;
|
|
1936
2014
|
}
|
|
1937
2015
|
throw new Error("Phase machine exited without a stop or abort transition.");
|
|
@@ -2084,7 +2162,7 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
|
|
|
2084
2162
|
},
|
|
2085
2163
|
async invokeModel(phaseContext) {
|
|
2086
2164
|
if (config.beforePrompt) {
|
|
2087
|
-
phaseContext = await config.beforePrompt(phase.
|
|
2165
|
+
phaseContext = await config.beforePrompt(phase.name, phaseContext);
|
|
2088
2166
|
}
|
|
2089
2167
|
const request = buildModelRequest({
|
|
2090
2168
|
systemPrompt: phaseContext.systemPrompt,
|
|
@@ -2111,7 +2189,7 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
|
|
|
2111
2189
|
config,
|
|
2112
2190
|
message: messageManager,
|
|
2113
2191
|
request,
|
|
2114
|
-
phaseId: phase.
|
|
2192
|
+
phaseId: phase.name
|
|
2115
2193
|
}),
|
|
2116
2194
|
{
|
|
2117
2195
|
signal: config.signal,
|
|
@@ -2133,7 +2211,7 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
|
|
|
2133
2211
|
}
|
|
2134
2212
|
)
|
|
2135
2213
|
);
|
|
2136
|
-
await config.onModelTranscript?.(result.transcript, { phase: phase.
|
|
2214
|
+
await config.onModelTranscript?.(result.transcript, { phase: phase.name, model: phase.model ?? config.model });
|
|
2137
2215
|
return result;
|
|
2138
2216
|
},
|
|
2139
2217
|
async executeTool(_context, toolCall) {
|
|
@@ -2166,7 +2244,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2166
2244
|
sourcePhaseId
|
|
2167
2245
|
},
|
|
2168
2246
|
state: {
|
|
2169
|
-
current: phase.
|
|
2247
|
+
current: phase.name,
|
|
2170
2248
|
available: Array.from(registry.phases.keys()),
|
|
2171
2249
|
iterations: 0,
|
|
2172
2250
|
payload
|
|
@@ -2185,7 +2263,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2185
2263
|
if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
|
|
2186
2264
|
const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
|
|
2187
2265
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2188
|
-
return { instanceId, phaseId: phase.
|
|
2266
|
+
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
2189
2267
|
}
|
|
2190
2268
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2191
2269
|
const entries = Array.from(backgroundTasks.entries());
|
|
@@ -2273,23 +2351,55 @@ function readSkillContent(skill) {
|
|
|
2273
2351
|
}
|
|
2274
2352
|
async function loadSkill(path) {
|
|
2275
2353
|
const resolved = resolveSkillPath(path);
|
|
2276
|
-
|
|
2354
|
+
let loaded;
|
|
2355
|
+
try {
|
|
2356
|
+
loaded = await loadMarkdown(resolved);
|
|
2357
|
+
} catch (error) {
|
|
2358
|
+
if (!(error instanceof FrontmatterParseError)) throw error;
|
|
2359
|
+
warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
|
|
2360
|
+
throw new ResourceMetadataError("parse_failed", error.message);
|
|
2361
|
+
}
|
|
2362
|
+
const { frontmatter, body } = loaded;
|
|
2363
|
+
const metadata = frontmatter;
|
|
2364
|
+
const id = inferResourceName(resolved, SKILL_MARKER);
|
|
2365
|
+
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
2366
|
+
const name = frontmatterName || id;
|
|
2367
|
+
const description = validateDescription(metadata.description);
|
|
2368
|
+
const diagnostics = validateResourceId(id);
|
|
2369
|
+
if (frontmatterName && frontmatterName !== id) {
|
|
2370
|
+
diagnostics.push(...validateResourceName(frontmatterName, id));
|
|
2371
|
+
}
|
|
2372
|
+
diagnostics.push(...description.warnings);
|
|
2373
|
+
warnResourceDiagnostics("skill", resolved, diagnostics);
|
|
2374
|
+
if (description.missing) {
|
|
2375
|
+
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
2376
|
+
}
|
|
2277
2377
|
return {
|
|
2278
|
-
name
|
|
2279
|
-
description:
|
|
2378
|
+
name,
|
|
2379
|
+
description: description.description,
|
|
2280
2380
|
filePath: resolved,
|
|
2281
2381
|
baseDir: dirname4(resolved),
|
|
2282
2382
|
content: body,
|
|
2283
|
-
disableModelInvocation:
|
|
2383
|
+
disableModelInvocation: metadata["disable-model-invocation"] === true
|
|
2284
2384
|
};
|
|
2285
2385
|
}
|
|
2286
2386
|
async function loadSkills(targetPath) {
|
|
2287
2387
|
const skillsDir = resolve4(targetPath);
|
|
2288
2388
|
if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
2289
|
-
|
|
2389
|
+
try {
|
|
2390
|
+
return [await loadSkill(skillsDir)];
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
if (error instanceof ResourceMetadataError) return [];
|
|
2393
|
+
throw error;
|
|
2394
|
+
}
|
|
2290
2395
|
}
|
|
2291
2396
|
if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
|
|
2292
|
-
|
|
2397
|
+
try {
|
|
2398
|
+
return [await loadSkill(skillsDir)];
|
|
2399
|
+
} catch (error) {
|
|
2400
|
+
if (error instanceof ResourceMetadataError) return [];
|
|
2401
|
+
throw error;
|
|
2402
|
+
}
|
|
2293
2403
|
}
|
|
2294
2404
|
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
2295
2405
|
const skills = [];
|
|
@@ -2301,6 +2411,7 @@ async function loadSkills(targetPath) {
|
|
|
2301
2411
|
const skill = await loadSkill(skillFile);
|
|
2302
2412
|
skills.push(skill);
|
|
2303
2413
|
} catch (error) {
|
|
2414
|
+
if (error instanceof ResourceMetadataError) continue;
|
|
2304
2415
|
console.warn(`Failed to load skill "${entry.name}":`, error);
|
|
2305
2416
|
}
|
|
2306
2417
|
}
|
|
@@ -2794,8 +2905,8 @@ var ExtensionRunner = class {
|
|
|
2794
2905
|
// ---------------------------------------------------------------------------
|
|
2795
2906
|
// Phase management
|
|
2796
2907
|
// ---------------------------------------------------------------------------
|
|
2797
|
-
getPhase(
|
|
2798
|
-
const reg = this.getRegisteredPhase(
|
|
2908
|
+
getPhase(name) {
|
|
2909
|
+
const reg = this.getRegisteredPhase(name);
|
|
2799
2910
|
if (!reg) return void 0;
|
|
2800
2911
|
return this.adaptToPhase(reg);
|
|
2801
2912
|
}
|
|
@@ -2807,8 +2918,8 @@ var ExtensionRunner = class {
|
|
|
2807
2918
|
createPhaseRegistry(input = {}) {
|
|
2808
2919
|
const registered = this.collectRegisteredPhases();
|
|
2809
2920
|
const phases = /* @__PURE__ */ new Map();
|
|
2810
|
-
for (const [
|
|
2811
|
-
phases.set(
|
|
2921
|
+
for (const [name, reg] of registered) {
|
|
2922
|
+
phases.set(name, this.adaptToPhase(reg));
|
|
2812
2923
|
}
|
|
2813
2924
|
const entryPhaseId = input.entryPhaseId ?? null;
|
|
2814
2925
|
return { phases, entryPhaseId };
|
|
@@ -2817,9 +2928,8 @@ var ExtensionRunner = class {
|
|
|
2817
2928
|
adaptToPhase(reg) {
|
|
2818
2929
|
const def = reg.definition;
|
|
2819
2930
|
return {
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
description: def.description ?? "",
|
|
2931
|
+
name: def.name,
|
|
2932
|
+
description: def.description,
|
|
2823
2933
|
tools: def.tools,
|
|
2824
2934
|
skills: def.skills,
|
|
2825
2935
|
target: def.target,
|
|
@@ -3086,22 +3196,27 @@ var ExtensionRunner = class {
|
|
|
3086
3196
|
});
|
|
3087
3197
|
}
|
|
3088
3198
|
registerPhase(extension, registration) {
|
|
3089
|
-
if (
|
|
3090
|
-
throw new Error(`Phase registration requires
|
|
3091
|
-
}
|
|
3092
|
-
|
|
3093
|
-
|
|
3199
|
+
if (typeof registration.name !== "string" || registration.name.length === 0) {
|
|
3200
|
+
throw new Error(`Phase registration requires a "name" field.`);
|
|
3201
|
+
}
|
|
3202
|
+
const name = registration.name;
|
|
3203
|
+
const description = validateDescription(registration.description);
|
|
3204
|
+
const errors = validateResourceId(name, "name");
|
|
3205
|
+
if (description.missing) errors.push(...description.warnings);
|
|
3206
|
+
errors.push(...validateSkillReferences(registration.skills));
|
|
3207
|
+
errors.push(...validatePhaseTarget(registration.target));
|
|
3208
|
+
if (errors.length > 0) {
|
|
3209
|
+
throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
|
|
3094
3210
|
}
|
|
3095
|
-
if (
|
|
3096
|
-
|
|
3211
|
+
if (description.warnings.length > 0) {
|
|
3212
|
+
warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
|
|
3097
3213
|
}
|
|
3098
|
-
if (this.phases.has(
|
|
3099
|
-
throw new Error(`Duplicate phase
|
|
3214
|
+
if (this.phases.has(name)) {
|
|
3215
|
+
throw new Error(`Duplicate phase name: ${name}`);
|
|
3100
3216
|
}
|
|
3101
3217
|
const definition = {
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
description: registration.description,
|
|
3218
|
+
name,
|
|
3219
|
+
description: description.description,
|
|
3105
3220
|
run: registration.run,
|
|
3106
3221
|
...registration.model ? { model: registration.model } : {}
|
|
3107
3222
|
};
|
|
@@ -3109,8 +3224,8 @@ var ExtensionRunner = class {
|
|
|
3109
3224
|
definition,
|
|
3110
3225
|
source: { extensionPath: extension.path }
|
|
3111
3226
|
};
|
|
3112
|
-
this.phases.set(
|
|
3113
|
-
extension.phases.set(
|
|
3227
|
+
this.phases.set(name, registered);
|
|
3228
|
+
extension.phases.set(name, registered);
|
|
3114
3229
|
this._phaseCache = null;
|
|
3115
3230
|
}
|
|
3116
3231
|
registerProvider(config) {
|
|
@@ -3137,8 +3252,8 @@ var ExtensionRunner = class {
|
|
|
3137
3252
|
}
|
|
3138
3253
|
this.pendingProviders.length = 0;
|
|
3139
3254
|
}
|
|
3140
|
-
getRegisteredPhase(
|
|
3141
|
-
return this.collectRegisteredPhases().get(
|
|
3255
|
+
getRegisteredPhase(name) {
|
|
3256
|
+
return this.collectRegisteredPhases().get(name);
|
|
3142
3257
|
}
|
|
3143
3258
|
collectRegisteredPhases() {
|
|
3144
3259
|
if (this._phaseCache) return this._phaseCache;
|
|
@@ -4472,6 +4587,22 @@ var InMemorySessionStore = class {
|
|
|
4472
4587
|
async open(sessionId) {
|
|
4473
4588
|
return this.sessions.get(sessionId);
|
|
4474
4589
|
}
|
|
4590
|
+
async list() {
|
|
4591
|
+
return Promise.all([...this.sessions.values()].map(async (session) => {
|
|
4592
|
+
const header = await session.getHeader();
|
|
4593
|
+
const entries = "listEntries" in session && typeof session.listEntries === "function" ? await session.listEntries() : [];
|
|
4594
|
+
return {
|
|
4595
|
+
id: header.id,
|
|
4596
|
+
title: header.title,
|
|
4597
|
+
createdAt: header.createdAt,
|
|
4598
|
+
updatedAt: header.updatedAt,
|
|
4599
|
+
messageCount: entries.filter((entry) => entry.type === "message").length
|
|
4600
|
+
};
|
|
4601
|
+
}));
|
|
4602
|
+
}
|
|
4603
|
+
async delete(sessionId) {
|
|
4604
|
+
return this.sessions.delete(sessionId);
|
|
4605
|
+
}
|
|
4475
4606
|
};
|
|
4476
4607
|
var JsonlSessionStore = class {
|
|
4477
4608
|
constructor(sessionsDir) {
|
|
@@ -4487,6 +4618,9 @@ var JsonlSessionStore = class {
|
|
|
4487
4618
|
list() {
|
|
4488
4619
|
return LocalJsonlSessionManager.list(this.sessionsDir);
|
|
4489
4620
|
}
|
|
4621
|
+
delete(sessionId) {
|
|
4622
|
+
return LocalJsonlSessionManager.delete(this.sessionsDir, sessionId);
|
|
4623
|
+
}
|
|
4490
4624
|
};
|
|
4491
4625
|
|
|
4492
4626
|
// src/runtime/agent-runtime.ts
|
|
@@ -4762,6 +4896,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4762
4896
|
toolRuntime;
|
|
4763
4897
|
bindings = /* @__PURE__ */ new Map();
|
|
4764
4898
|
agents = /* @__PURE__ */ new Map();
|
|
4899
|
+
invalidatedAgents = /* @__PURE__ */ new Set();
|
|
4765
4900
|
runHandles = /* @__PURE__ */ new Map();
|
|
4766
4901
|
runWaiters = /* @__PURE__ */ new Map();
|
|
4767
4902
|
pendingRuns = [];
|
|
@@ -4808,6 +4943,15 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4808
4943
|
const runtime = new _AgentRuntime(options);
|
|
4809
4944
|
activeRuntime = runtime;
|
|
4810
4945
|
try {
|
|
4946
|
+
if (options.sessionProvider) {
|
|
4947
|
+
for (const record of await options.stateStore.listAgents()) {
|
|
4948
|
+
if (record.lifecycleState !== "deleting") continue;
|
|
4949
|
+
const activeRuns = await options.stateStore.listRuns({ agentId: record.id, states: ["queued", "running", "suspended"] });
|
|
4950
|
+
if (activeRuns.length > 0) continue;
|
|
4951
|
+
await options.stateStore.deleteAgentData(record.id);
|
|
4952
|
+
await options.sessionProvider.delete(record.sessionId);
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4811
4955
|
await runtime.recover();
|
|
4812
4956
|
runtime.startLeaseRecovery();
|
|
4813
4957
|
return runtime;
|
|
@@ -4826,8 +4970,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4826
4970
|
input: "",
|
|
4827
4971
|
skills: options.context.skills
|
|
4828
4972
|
});
|
|
4973
|
+
const header = await manager.getHeader();
|
|
4829
4974
|
const record = await this.stateStore.createAgent({
|
|
4830
|
-
sessionId: manager.getSessionId()
|
|
4975
|
+
sessionId: manager.getSessionId(),
|
|
4976
|
+
parentSessionId: header.parentSessionId
|
|
4831
4977
|
});
|
|
4832
4978
|
this.pausedAgents.delete(record.id);
|
|
4833
4979
|
const agent = await this.attachAgent(record, manager, options);
|
|
@@ -4839,6 +4985,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4839
4985
|
assertAgentOptions(options);
|
|
4840
4986
|
const record = await this.stateStore.getAgent(agentId);
|
|
4841
4987
|
if (!record) throw new Error(`Agent not found: ${agentId}.`);
|
|
4988
|
+
if (record.lifecycleState !== "active") throw new Error(`Session ${record.sessionId} is ${record.lifecycleState}.`);
|
|
4842
4989
|
if (record.state === "paused") this.pausedAgents.add(record.id);
|
|
4843
4990
|
else this.pausedAgents.delete(record.id);
|
|
4844
4991
|
const provider = this.sessionProvider;
|
|
@@ -4947,6 +5094,82 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4947
5094
|
async listRuns(input) {
|
|
4948
5095
|
return this.stateStore.listRuns(input);
|
|
4949
5096
|
}
|
|
5097
|
+
async listSessions(input = {}) {
|
|
5098
|
+
const records = await this.stateStore.listAgents();
|
|
5099
|
+
const items = this.sessionProvider ? await this.sessionProvider.list() : [];
|
|
5100
|
+
const summaries = new Map(items.map((item) => [item.id, item]));
|
|
5101
|
+
const activeRuns = await this.stateStore.listActiveRuns();
|
|
5102
|
+
return records.filter((record) => !input.state || record.lifecycleState === input.state).map((record) => {
|
|
5103
|
+
const item = summaries.get(record.sessionId);
|
|
5104
|
+
return {
|
|
5105
|
+
id: record.sessionId,
|
|
5106
|
+
state: record.lifecycleState,
|
|
5107
|
+
createdAt: record.createdAt,
|
|
5108
|
+
updatedAt: item?.updatedAt ?? record.updatedAt,
|
|
5109
|
+
...record.lifecycleState === "archived" ? { archivedAt: record.updatedAt } : {},
|
|
5110
|
+
...record.parentSessionId ? { parentSessionId: record.parentSessionId } : {},
|
|
5111
|
+
agentId: record.id,
|
|
5112
|
+
activeRunCount: activeRuns.filter((run) => run.agentId === record.id).length,
|
|
5113
|
+
...item?.title ? { title: item.title } : {}
|
|
5114
|
+
};
|
|
5115
|
+
});
|
|
5116
|
+
}
|
|
5117
|
+
async archiveSession(sessionId) {
|
|
5118
|
+
const record = await this.requireSessionAgent(sessionId);
|
|
5119
|
+
if (record.lifecycleState === "archived") return;
|
|
5120
|
+
if (record.lifecycleState === "deleting") throw new Error(`Session ${sessionId} is being deleted.`);
|
|
5121
|
+
await this.stateStore.setAgentLifecycleState(record.id, "archived");
|
|
5122
|
+
this.publishEvents();
|
|
5123
|
+
}
|
|
5124
|
+
async unarchiveSession(sessionId) {
|
|
5125
|
+
const record = await this.requireSessionAgent(sessionId);
|
|
5126
|
+
if (record.lifecycleState === "active") return;
|
|
5127
|
+
if (record.lifecycleState === "deleting") throw new Error(`Session ${sessionId} is being deleted.`);
|
|
5128
|
+
await this.stateStore.setAgentLifecycleState(record.id, "active");
|
|
5129
|
+
await this.scheduleQueuedRuns(record.id);
|
|
5130
|
+
this.publishEvents();
|
|
5131
|
+
}
|
|
5132
|
+
async deleteSession(sessionId) {
|
|
5133
|
+
this.assertRunning();
|
|
5134
|
+
if (!this.sessionProvider) throw new Error("Agent Runtime requires a SessionManager provider to delete a Session.");
|
|
5135
|
+
if (!this.sessionProvider.delete) throw new Error("Session Manager provider does not support deletion.");
|
|
5136
|
+
const root = await this.requireSessionAgent(sessionId);
|
|
5137
|
+
const records = await this.stateStore.listAgents();
|
|
5138
|
+
const subtree = /* @__PURE__ */ new Set([root.id]);
|
|
5139
|
+
let changed = true;
|
|
5140
|
+
while (changed) {
|
|
5141
|
+
changed = false;
|
|
5142
|
+
for (const record of records) {
|
|
5143
|
+
if (record.parentSessionId && records.some((candidate) => subtree.has(candidate.id) && candidate.sessionId === record.parentSessionId) && !subtree.has(record.id)) {
|
|
5144
|
+
subtree.add(record.id);
|
|
5145
|
+
changed = true;
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
const activeRuns = await this.stateStore.listActiveRuns();
|
|
5150
|
+
if (activeRuns.some((run) => subtree.has(run.agentId))) {
|
|
5151
|
+
throw new Error(`Session ${sessionId} has active Runs and cannot be deleted.`);
|
|
5152
|
+
}
|
|
5153
|
+
const targets = records.filter((record) => subtree.has(record.id));
|
|
5154
|
+
for (const record of targets) await this.stateStore.setAgentLifecycleState(record.id, "deleting");
|
|
5155
|
+
for (const record of targets) {
|
|
5156
|
+
this.bindings.delete(record.id);
|
|
5157
|
+
this.agents.delete(record.id);
|
|
5158
|
+
this.invalidatedAgents.add(record.id);
|
|
5159
|
+
this.pausedAgents.delete(record.id);
|
|
5160
|
+
for (let index = this.pendingRuns.length - 1; index >= 0; index -= 1) {
|
|
5161
|
+
if (this.pendingRuns[index]?.agentId === record.id) this.pendingRuns.splice(index, 1);
|
|
5162
|
+
}
|
|
5163
|
+
await this.stateStore.deleteAgentData(record.id);
|
|
5164
|
+
await this.sessionProvider.delete(record.sessionId);
|
|
5165
|
+
}
|
|
5166
|
+
this.publishEvents();
|
|
5167
|
+
}
|
|
5168
|
+
async requireSessionAgent(sessionId) {
|
|
5169
|
+
const record = await this.stateStore.getAgentBySessionId(sessionId);
|
|
5170
|
+
if (!record) throw new Error(`Session not found: ${sessionId}.`);
|
|
5171
|
+
return record;
|
|
5172
|
+
}
|
|
4950
5173
|
getAgent(agentId) {
|
|
4951
5174
|
return this.agents.get(agentId);
|
|
4952
5175
|
}
|
|
@@ -5079,7 +5302,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5079
5302
|
}
|
|
5080
5303
|
async submitAgentInput(agentId, input, persist) {
|
|
5081
5304
|
this.assertRunning();
|
|
5305
|
+
if (this.invalidatedAgents.has(agentId)) throw new Error(`Session for Agent ${agentId} was deleted.`);
|
|
5082
5306
|
if (!this.bindings.has(agentId)) throw new Error(`Agent ${agentId} has no live Agent Binding.`);
|
|
5307
|
+
const record = await this.stateStore.getAgent(agentId);
|
|
5308
|
+
if (!record) throw new Error(`Agent ${agentId} was deleted.`);
|
|
5309
|
+
if (record.lifecycleState !== "active") throw new Error(`Session ${record.sessionId} is ${record.lifecycleState}.`);
|
|
5083
5310
|
const enqueued = await this.stateStore.enqueueAgentInput({ agentId, input });
|
|
5084
5311
|
await persist();
|
|
5085
5312
|
if (enqueued.resumed) this.notifyRun(enqueued.run);
|
|
@@ -5335,6 +5562,8 @@ var InMemoryRuntimeStateStore = class {
|
|
|
5335
5562
|
id: createId("agt"),
|
|
5336
5563
|
sessionId: input.sessionId,
|
|
5337
5564
|
state: "active",
|
|
5565
|
+
lifecycleState: "active",
|
|
5566
|
+
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
5338
5567
|
createdAt: timestamp,
|
|
5339
5568
|
updatedAt: timestamp
|
|
5340
5569
|
};
|
|
@@ -5346,6 +5575,10 @@ var InMemoryRuntimeStateStore = class {
|
|
|
5346
5575
|
const agent = this.agents.get(agentId);
|
|
5347
5576
|
return agent ? clone2(agent) : void 0;
|
|
5348
5577
|
}
|
|
5578
|
+
async getAgentBySessionId(sessionId) {
|
|
5579
|
+
const agent = [...this.agents.values()].find((candidate) => candidate.sessionId === sessionId);
|
|
5580
|
+
return agent ? clone2(agent) : void 0;
|
|
5581
|
+
}
|
|
5349
5582
|
async listAgents() {
|
|
5350
5583
|
return clone2([...this.agents.values()]);
|
|
5351
5584
|
}
|
|
@@ -5358,6 +5591,27 @@ var InMemoryRuntimeStateStore = class {
|
|
|
5358
5591
|
this.recordEvent(state === "paused" ? "agent_paused" : "agent_resumed", { agentId });
|
|
5359
5592
|
return clone2(agent);
|
|
5360
5593
|
}
|
|
5594
|
+
async setAgentLifecycleState(agentId, state) {
|
|
5595
|
+
const agent = this.requireAgent(agentId);
|
|
5596
|
+
if (agent.lifecycleState === state) return clone2(agent);
|
|
5597
|
+
agent.lifecycleState = state;
|
|
5598
|
+
agent.updatedAt = createTimestamp();
|
|
5599
|
+
if (state === "archived") this.recordEvent("session_archived", { agentId });
|
|
5600
|
+
if (state === "active") this.recordEvent("session_unarchived", { agentId });
|
|
5601
|
+
return clone2(agent);
|
|
5602
|
+
}
|
|
5603
|
+
async deleteAgentData(agentId) {
|
|
5604
|
+
this.requireAgent(agentId);
|
|
5605
|
+
const runIds = new Set([...this.runs.values()].filter((run) => run.agentId === agentId).map((run) => run.id));
|
|
5606
|
+
for (const [id, call] of this.toolCalls) if (call.agentId === agentId || runIds.has(call.runId)) this.toolCalls.delete(id);
|
|
5607
|
+
for (const [id, run] of this.runs) if (run.agentId === agentId) this.runs.delete(id);
|
|
5608
|
+
for (const [id, message] of this.messages) if (message.agentId === agentId) this.messages.delete(id);
|
|
5609
|
+
for (let index = this.events.length - 1; index >= 0; index -= 1) {
|
|
5610
|
+
const event = this.events[index];
|
|
5611
|
+
if (event?.agentId === agentId || event?.runId && runIds.has(event.runId)) this.events.splice(index, 1);
|
|
5612
|
+
}
|
|
5613
|
+
this.agents.delete(agentId);
|
|
5614
|
+
}
|
|
5361
5615
|
async enqueueAgentInput(input) {
|
|
5362
5616
|
return this.enqueueAgentInputState(input);
|
|
5363
5617
|
}
|
|
@@ -5787,10 +6041,15 @@ import { Database } from "bun:sqlite";
|
|
|
5787
6041
|
|
|
5788
6042
|
// src/runtime/runtime-schema.ts
|
|
5789
6043
|
var RUNTIME_SCHEMA_SQL = `
|
|
6044
|
+
CREATE TABLE IF NOT EXISTS runtime_schema (
|
|
6045
|
+
version INTEGER PRIMARY KEY NOT NULL
|
|
6046
|
+
);
|
|
5790
6047
|
CREATE TABLE IF NOT EXISTS agents (
|
|
5791
6048
|
id TEXT PRIMARY KEY NOT NULL,
|
|
5792
6049
|
session_id TEXT NOT NULL,
|
|
6050
|
+
parent_session_id TEXT,
|
|
5793
6051
|
state TEXT NOT NULL CHECK (state IN ('active', 'paused')),
|
|
6052
|
+
lifecycle_state TEXT NOT NULL CHECK (lifecycle_state IN ('active', 'archived', 'deleting')),
|
|
5794
6053
|
created_at TEXT NOT NULL,
|
|
5795
6054
|
updated_at TEXT NOT NULL
|
|
5796
6055
|
);
|
|
@@ -5873,6 +6132,8 @@ var RUNTIME_SCHEMA_SQL = `
|
|
|
5873
6132
|
|
|
5874
6133
|
CREATE INDEX IF NOT EXISTS agent_runs_runnable_idx
|
|
5875
6134
|
ON agent_runs (state, created_at);
|
|
6135
|
+
CREATE UNIQUE INDEX IF NOT EXISTS agents_session_id_unique
|
|
6136
|
+
ON agents (session_id);
|
|
5876
6137
|
`;
|
|
5877
6138
|
function initializeRuntimeSchema(database) {
|
|
5878
6139
|
const initialize = database.transaction(() => {
|
|
@@ -5889,6 +6150,7 @@ function initializeRuntimeSchema(database) {
|
|
|
5889
6150
|
}
|
|
5890
6151
|
});
|
|
5891
6152
|
initialize();
|
|
6153
|
+
database.run("INSERT OR IGNORE INTO runtime_schema (version) VALUES (2)");
|
|
5892
6154
|
}
|
|
5893
6155
|
|
|
5894
6156
|
// src/runtime/sqlite-store.ts
|
|
@@ -5932,13 +6194,15 @@ var SqliteRuntimeStateStore = class {
|
|
|
5932
6194
|
id: createId("agt"),
|
|
5933
6195
|
sessionId: input.sessionId,
|
|
5934
6196
|
state: "active",
|
|
6197
|
+
lifecycleState: "active",
|
|
6198
|
+
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
5935
6199
|
createdAt: timestamp,
|
|
5936
6200
|
updatedAt: timestamp
|
|
5937
6201
|
};
|
|
5938
6202
|
const create = this.database.transaction(() => {
|
|
5939
6203
|
this.database.run(
|
|
5940
|
-
"INSERT INTO agents (id, session_id, state, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
5941
|
-
[agent.id, agent.sessionId, agent.state, agent.createdAt, agent.updatedAt]
|
|
6204
|
+
"INSERT INTO agents (id, session_id, parent_session_id, state, lifecycle_state, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
6205
|
+
[agent.id, agent.sessionId, agent.parentSessionId ?? null, agent.state, agent.lifecycleState, agent.createdAt, agent.updatedAt]
|
|
5942
6206
|
);
|
|
5943
6207
|
this.recordEvent("agent_created", { agentId: agent.id });
|
|
5944
6208
|
});
|
|
@@ -5949,6 +6213,10 @@ var SqliteRuntimeStateStore = class {
|
|
|
5949
6213
|
const row = this.database.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
5950
6214
|
return row ? this.agentFromRow(row) : void 0;
|
|
5951
6215
|
}
|
|
6216
|
+
async getAgentBySessionId(sessionId) {
|
|
6217
|
+
const row = this.database.query("SELECT * FROM agents WHERE session_id = ?").get(sessionId);
|
|
6218
|
+
return row ? this.agentFromRow(row) : void 0;
|
|
6219
|
+
}
|
|
5952
6220
|
async listAgents() {
|
|
5953
6221
|
const rows = this.database.query("SELECT * FROM agents ORDER BY created_at, id").all();
|
|
5954
6222
|
return rows.map((row) => this.agentFromRow(row));
|
|
@@ -5964,6 +6232,34 @@ var SqliteRuntimeStateStore = class {
|
|
|
5964
6232
|
})();
|
|
5965
6233
|
return clone3(updated);
|
|
5966
6234
|
}
|
|
6235
|
+
async setAgentLifecycleState(agentId, state) {
|
|
6236
|
+
const current = this.requireAgent(agentId);
|
|
6237
|
+
if (current.lifecycleState === state) return clone3(current);
|
|
6238
|
+
const { timestamp } = timestampFor();
|
|
6239
|
+
const updated = this.database.transaction(() => {
|
|
6240
|
+
this.database.run("UPDATE agents SET lifecycle_state = ?, updated_at = ? WHERE id = ?", [state, timestamp, agentId]);
|
|
6241
|
+
if (state === "archived") this.recordEvent("session_archived", { agentId });
|
|
6242
|
+
if (state === "active" && current.lifecycleState === "archived") this.recordEvent("session_unarchived", { agentId });
|
|
6243
|
+
return this.requireAgent(agentId);
|
|
6244
|
+
})();
|
|
6245
|
+
return clone3(updated);
|
|
6246
|
+
}
|
|
6247
|
+
async deleteAgentData(agentId) {
|
|
6248
|
+
this.database.transaction(() => {
|
|
6249
|
+
const agent = this.requireAgent(agentId);
|
|
6250
|
+
const runIds = this.database.query("SELECT id FROM agent_runs WHERE agent_id = ?").all(agentId).map((row) => row.id);
|
|
6251
|
+
if (runIds.length > 0) {
|
|
6252
|
+
const marks = runIds.map(() => "?").join(",");
|
|
6253
|
+
this.database.run(`DELETE FROM runtime_tool_calls WHERE run_id IN (${marks})`, runIds);
|
|
6254
|
+
this.database.run(`DELETE FROM runtime_leases WHERE run_id IN (${marks})`, runIds);
|
|
6255
|
+
this.database.run(`DELETE FROM runtime_events WHERE agent_id = ? OR run_id IN (${marks})`, [agentId, ...runIds]);
|
|
6256
|
+
this.database.run(`DELETE FROM agent_runs WHERE id IN (${marks})`, runIds);
|
|
6257
|
+
}
|
|
6258
|
+
this.database.run("DELETE FROM runtime_messages WHERE agent_id = ?", [agentId]);
|
|
6259
|
+
this.database.run("DELETE FROM runtime_events WHERE agent_id = ?", [agentId]);
|
|
6260
|
+
this.database.run("DELETE FROM agents WHERE id = ?", [agentId]);
|
|
6261
|
+
})();
|
|
6262
|
+
}
|
|
5967
6263
|
async enqueueAgentInput(input) {
|
|
5968
6264
|
const enqueue = this.database.transaction(() => this.enqueueAgentInputState(input));
|
|
5969
6265
|
return clone3(enqueue());
|
|
@@ -6453,6 +6749,8 @@ var SqliteRuntimeStateStore = class {
|
|
|
6453
6749
|
id: row.id,
|
|
6454
6750
|
sessionId: row.session_id,
|
|
6455
6751
|
state: row.state,
|
|
6752
|
+
lifecycleState: row.lifecycle_state,
|
|
6753
|
+
...row.parent_session_id ? { parentSessionId: row.parent_session_id } : {},
|
|
6456
6754
|
createdAt: row.created_at,
|
|
6457
6755
|
updatedAt: row.updated_at
|
|
6458
6756
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowan-agent/agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@rowan-agent/models": "0.6.2",
|
|
27
27
|
"jiti": "2.7.0",
|
|
28
|
-
"typebox": "^1.0.0"
|
|
28
|
+
"typebox": "^1.0.0",
|
|
29
|
+
"yaml": "^2.7.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"tsup": "^8.5.1"
|