@frockbot/kernel-agent-loop 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +26 -6
- package/src/agent.ts +172 -0
- package/src/index.test.ts +2891 -0
- package/src/index.ts +1219 -0
- package/tsconfig.json +13 -0
- package/README.md +0 -3
package/package.json
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/kernel-agent-loop",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./agent": "./src/agent.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "bun test src",
|
|
12
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@frockbot/kernel-contracts": "0.1.1",
|
|
16
|
+
"cordis": "4.0.0-rc.8"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@frockbot/plugin-models": "0.1.1",
|
|
20
|
+
"@frockbot/plugin-prompt": "0.1.1",
|
|
21
|
+
"@frockbot/plugin-tools": "0.1.1",
|
|
22
|
+
"@types/bun": "1.4.0",
|
|
23
|
+
"@types/node": "26.2.0",
|
|
24
|
+
"typescript": "^7.0.2"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
6
29
|
"repository": {
|
|
7
30
|
"type": "git",
|
|
8
31
|
"url": "git+https://github.com/timoconnellaus/frockbot.git",
|
|
9
32
|
"directory": "packages/kernel-agent-loop"
|
|
10
|
-
},
|
|
11
|
-
"publishConfig": {
|
|
12
|
-
"access": "public"
|
|
13
33
|
}
|
|
14
34
|
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { type Context, Service } from "cordis";
|
|
2
|
+
import type {
|
|
3
|
+
ModelBindingSnapshot,
|
|
4
|
+
NormalizedModelRequest,
|
|
5
|
+
Session,
|
|
6
|
+
SkillRefV1,
|
|
7
|
+
TurnTypeV1,
|
|
8
|
+
} from "@frockbot/kernel-contracts";
|
|
9
|
+
|
|
10
|
+
export type AgentStatus = "idle" | "running" | "disposed";
|
|
11
|
+
|
|
12
|
+
/** One exact new external effect whose durable intent is already journaled. */
|
|
13
|
+
export type AgentEffectAdmission =
|
|
14
|
+
{ kind: "model"; effectId: string } | { kind: "tool"; effectId: string };
|
|
15
|
+
|
|
16
|
+
export interface AgentOptions {
|
|
17
|
+
botId: string;
|
|
18
|
+
agentId?: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
provider: string;
|
|
21
|
+
model: string;
|
|
22
|
+
/**
|
|
23
|
+
* The kind of Turn this Agent's runs are admitted as. It selects the tool
|
|
24
|
+
* catalog and nothing else; the kernel carries the value and holds no
|
|
25
|
+
* opinion about what any turn type admits. Defaults to `chat`, which is what
|
|
26
|
+
* every Turn recorded before turn admission existed replays as.
|
|
27
|
+
*/
|
|
28
|
+
turnType?: TurnTypeV1;
|
|
29
|
+
/**
|
|
30
|
+
* The subagent role this Agent's runs are admitted under. A second ceiling
|
|
31
|
+
* dimension on the same terms as `turnType`: it selects the tool catalog and
|
|
32
|
+
* nothing else, and the kernel holds no opinion about what a role name
|
|
33
|
+
* means. Only meaningful on a `subagent` Turn; absent means no narrowing.
|
|
34
|
+
*/
|
|
35
|
+
subagentRole?: string;
|
|
36
|
+
/** Durably linearizes each new effect against Stop immediately before use. */
|
|
37
|
+
admitEffect(effect: AgentEffectAdmission): Promise<boolean>;
|
|
38
|
+
modelBinding?: ModelBindingSnapshot;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AgentInput {
|
|
42
|
+
messageId: string;
|
|
43
|
+
text: string;
|
|
44
|
+
/**
|
|
45
|
+
* The Skills this input invoked from the composer. The kernel carries the
|
|
46
|
+
* refs and resolves nothing: which Skill a ref names, and what happens to
|
|
47
|
+
* its body, is the Skills Package's policy, read off this field in
|
|
48
|
+
* `agent/pre-step`.
|
|
49
|
+
*/
|
|
50
|
+
skills?: SkillRefV1[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What `Agent.send` accepts: bare text, or text with invoked Skills. */
|
|
54
|
+
export interface AgentSendV1 {
|
|
55
|
+
text: string;
|
|
56
|
+
skills?: readonly SkillRefV1[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type PreStepDecision =
|
|
60
|
+
{ kind: "enter"; inputs: AgentInput[] } | { kind: "reject"; reason: string };
|
|
61
|
+
|
|
62
|
+
export type RequestErrorAction = { kind: "retry" } | { kind: "fail" };
|
|
63
|
+
|
|
64
|
+
export interface Agent {
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly botId: string;
|
|
67
|
+
readonly session: Session;
|
|
68
|
+
readonly status: AgentStatus;
|
|
69
|
+
send(input: string | AgentSendV1): string;
|
|
70
|
+
resume(): void;
|
|
71
|
+
cancel(reason?: "user" | "shutdown"): void;
|
|
72
|
+
whenIdle(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface AgentHandle {
|
|
76
|
+
agent: Agent;
|
|
77
|
+
dispose(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface AgentFactory {
|
|
81
|
+
create(options: AgentOptions): Promise<AgentHandle>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
declare module "cordis" {
|
|
85
|
+
interface Context {
|
|
86
|
+
agents: AgentRegistry;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface Events {
|
|
90
|
+
"agent/created": (agent: Agent) => void;
|
|
91
|
+
"agent/disposed": (agent: Agent) => void;
|
|
92
|
+
"agent/status": (agent: Agent, status: AgentStatus) => void;
|
|
93
|
+
"agent/inbox/inserted": (agent: Agent, input: AgentInput) => void;
|
|
94
|
+
"agent/inbox/claimed": (
|
|
95
|
+
agent: Agent,
|
|
96
|
+
inputs: AgentInput[],
|
|
97
|
+
turn: number,
|
|
98
|
+
) => void;
|
|
99
|
+
"agent/pre-step": (
|
|
100
|
+
agent: Agent,
|
|
101
|
+
inputs: AgentInput[],
|
|
102
|
+
turn: number,
|
|
103
|
+
step: number,
|
|
104
|
+
next: () => Promise<PreStepDecision>,
|
|
105
|
+
) => Promise<PreStepDecision>;
|
|
106
|
+
"agent/request": (
|
|
107
|
+
agent: Agent,
|
|
108
|
+
request: NormalizedModelRequest,
|
|
109
|
+
signal: AbortSignal,
|
|
110
|
+
next: () => Promise<NormalizedModelRequest>,
|
|
111
|
+
) => Promise<NormalizedModelRequest>;
|
|
112
|
+
"agent/request-error": (
|
|
113
|
+
agent: Agent,
|
|
114
|
+
error: unknown,
|
|
115
|
+
signal: AbortSignal,
|
|
116
|
+
next: () => Promise<RequestErrorAction>,
|
|
117
|
+
) => Promise<RequestErrorAction>;
|
|
118
|
+
"agent/model-outcome-committed": (
|
|
119
|
+
agent: Agent,
|
|
120
|
+
requestId: string,
|
|
121
|
+
outcome: "completed" | "not-started",
|
|
122
|
+
) => Promise<void>;
|
|
123
|
+
"agent/turn-stopping": (agent: Agent, turn: number) => Promise<void>;
|
|
124
|
+
"agent/cancel-requested": (
|
|
125
|
+
agent: Agent,
|
|
126
|
+
reason: "user" | "shutdown",
|
|
127
|
+
) => void;
|
|
128
|
+
"agent/error": (agent: Agent, error: unknown) => void;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class AgentRegistry extends Service {
|
|
133
|
+
private agents = new Map<string, Agent>();
|
|
134
|
+
private factory: AgentFactory | undefined;
|
|
135
|
+
|
|
136
|
+
constructor(ctx: Context) {
|
|
137
|
+
super(ctx, "agents");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setFactory(factory: AgentFactory): () => void {
|
|
141
|
+
if (this.factory) throw new Error("an agent factory is already registered");
|
|
142
|
+
this.factory = factory;
|
|
143
|
+
return () => {
|
|
144
|
+
if (this.factory === factory) this.factory = undefined;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
create(options: AgentOptions): Promise<AgentHandle> {
|
|
149
|
+
if (!this.factory) throw new Error("no agent factory is registered");
|
|
150
|
+
return this.factory.create(options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
register(agent: Agent): () => void {
|
|
154
|
+
if (this.agents.has(agent.id))
|
|
155
|
+
throw new Error(`agent "${agent.id}" already exists`);
|
|
156
|
+
this.agents.set(agent.id, agent);
|
|
157
|
+
this.ctx.emit("agent/created", agent);
|
|
158
|
+
return () => {
|
|
159
|
+
if (this.agents.get(agent.id) !== agent) return;
|
|
160
|
+
this.agents.delete(agent.id);
|
|
161
|
+
this.ctx.emit("agent/disposed", agent);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get(agentId: string): Agent | undefined {
|
|
166
|
+
return this.agents.get(agentId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
list(): Agent[] {
|
|
170
|
+
return [...this.agents.values()];
|
|
171
|
+
}
|
|
172
|
+
}
|