pi-fabric 0.74.0 → 0.76.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 +1 -0
- package/dist/actors/manager.d.ts.map +1 -1
- package/dist/agents/transports/process-utils.d.ts.map +1 -1
- package/dist/chunks/chunk-3SGRTVFP.js +60 -0
- package/dist/chunks/chunk-3SGRTVFP.js.map +7 -0
- package/dist/chunks/{chunk-ASUT6BU4.js → chunk-65RRHNBX.js} +7 -1
- package/dist/chunks/chunk-65RRHNBX.js.map +7 -0
- package/dist/chunks/{chunk-6F6YQ2DS.js → chunk-6BBFFEPO.js} +8 -8
- package/dist/chunks/{chunk-SJYOKQJT.js → chunk-6ZKYQW33.js} +4 -4
- package/dist/chunks/{chunk-NLY3MSQJ.js → chunk-BOIV6E3C.js} +5 -5
- package/dist/chunks/chunk-BT6UELLP.js +665 -0
- package/dist/chunks/chunk-BT6UELLP.js.map +7 -0
- package/dist/chunks/{chunk-M5ANI67T.js → chunk-F2GKEXGI.js} +20 -30
- package/dist/chunks/chunk-F2GKEXGI.js.map +7 -0
- package/dist/chunks/chunk-FRHBI5QB.js +87 -0
- package/dist/chunks/chunk-FRHBI5QB.js.map +7 -0
- package/dist/chunks/{chunk-YYODET6T.js → chunk-NI3ZXJZD.js} +4 -4
- package/dist/chunks/{chunk-GLEX5U5Q.js → chunk-QWIKS6CT.js} +3 -3
- package/dist/chunks/chunk-QWIKS6CT.js.map +7 -0
- package/dist/chunks/{chunk-6NYBEXUT.js → chunk-QXB75FLD.js} +32 -85
- package/dist/chunks/chunk-QXB75FLD.js.map +7 -0
- package/dist/chunks/{chunk-5OKJP5PD.js → chunk-USMTYVXY.js} +8 -8
- package/dist/chunks/chunk-USMTYVXY.js.map +7 -0
- package/dist/chunks/chunk-X2MKXKDA.js +17 -0
- package/dist/chunks/chunk-X2MKXKDA.js.map +7 -0
- package/dist/chunks/{core-override-guest-types-SENPG5IT.js → core-override-guest-types-ADPUUQWL.js} +2 -2
- package/dist/chunks/{dashboard-2RGAN2WT.js → dashboard-EEJW66FA.js} +4 -4
- package/dist/chunks/{fabric-runtime-state-FBDHAKPF.js → fabric-runtime-state-FBAJTXB2.js} +148 -94
- package/dist/chunks/fabric-runtime-state-FBAJTXB2.js.map +7 -0
- package/dist/chunks/{guest-types-NCGNPMCZ.js → guest-types-2P2LKCZE.js} +2 -2
- package/dist/chunks/{node-process-runtime-AFV55TEV.js → node-process-runtime-ITTLVGWY.js} +8 -8
- package/dist/chunks/{quickjs-runtime-C75KPH77.js → quickjs-runtime-6LAQLSB4.js} +3 -3
- package/dist/chunks/{settings-ISH7S23I.js → settings-VEXI347I.js} +4 -3
- package/dist/chunks/{settings-ISH7S23I.js.map → settings-VEXI347I.js.map} +1 -1
- package/dist/core/action-registry.js +2 -2
- package/dist/core/system-guidance.d.ts.map +1 -1
- package/dist/fabric-exec-arguments.d.ts +4 -0
- package/dist/fabric-exec-arguments.d.ts.map +1 -1
- package/dist/fabric-exec-tool.d.ts.map +1 -1
- package/dist/index.js +104 -50
- package/dist/index.js.map +2 -2
- package/dist/memory/search.js +2 -2
- package/dist/providers/agents-provider.d.ts.map +1 -1
- package/dist/providers/memory-provider.js +3 -3
- package/dist/residency/actor-client.d.ts +16 -0
- package/dist/residency/actor-client.d.ts.map +1 -0
- package/dist/residency/actor-client.js +9 -0
- package/dist/residency/actor-client.js.map +7 -0
- package/dist/residency/client.d.ts.map +1 -1
- package/dist/residency/host.d.ts +1 -24
- package/dist/residency/host.d.ts.map +1 -1
- package/dist/residency/host.js +11 -644
- package/dist/residency/host.js.map +3 -3
- package/dist/residency/launcher-owner.d.ts +7 -0
- package/dist/residency/launcher-owner.d.ts.map +1 -0
- package/dist/residency/launcher.d.ts +3 -0
- package/dist/residency/launcher.d.ts.map +1 -0
- package/dist/residency/launcher.js +154 -0
- package/dist/residency/launcher.js.map +7 -0
- package/dist/residency/pi-entry.d.ts +3 -0
- package/dist/residency/pi-entry.d.ts.map +1 -0
- package/dist/residency/pi-entry.js +41 -0
- package/dist/residency/pi-entry.js.map +7 -0
- package/dist/residency/protocol.d.ts +11 -1
- package/dist/residency/protocol.d.ts.map +1 -1
- package/dist/runtime/guest-types.d.ts +1 -1
- package/dist/runtime/guest-types.d.ts.map +1 -1
- package/dist/runtime/quickjs-runtime.d.ts +1 -1
- package/dist/runtime/quickjs-runtime.d.ts.map +1 -1
- package/dist/type-error-guidance.d.ts.map +1 -1
- package/docs/architecture.md +1 -1
- package/docs/residency-runtime.md +82 -0
- package/package.json +1 -1
- package/skills/fabric-exec/SKILL.md +1 -1
- package/dist/chunks/chunk-5OKJP5PD.js.map +0 -7
- package/dist/chunks/chunk-6NYBEXUT.js.map +0 -7
- package/dist/chunks/chunk-ASUT6BU4.js.map +0 -7
- package/dist/chunks/chunk-GLEX5U5Q.js.map +0 -7
- package/dist/chunks/chunk-M5ANI67T.js.map +0 -7
- package/dist/chunks/fabric-runtime-state-FBDHAKPF.js.map +0 -7
- /package/dist/chunks/{chunk-6F6YQ2DS.js.map → chunk-6BBFFEPO.js.map} +0 -0
- /package/dist/chunks/{chunk-SJYOKQJT.js.map → chunk-6ZKYQW33.js.map} +0 -0
- /package/dist/chunks/{chunk-NLY3MSQJ.js.map → chunk-BOIV6E3C.js.map} +0 -0
- /package/dist/chunks/{chunk-YYODET6T.js.map → chunk-NI3ZXJZD.js.map} +0 -0
- /package/dist/chunks/{core-override-guest-types-SENPG5IT.js.map → core-override-guest-types-ADPUUQWL.js.map} +0 -0
- /package/dist/chunks/{dashboard-2RGAN2WT.js.map → dashboard-EEJW66FA.js.map} +0 -0
- /package/dist/chunks/{guest-types-NCGNPMCZ.js.map → guest-types-2P2LKCZE.js.map} +0 -0
- /package/dist/chunks/{node-process-runtime-AFV55TEV.js.map → node-process-runtime-ITTLVGWY.js.map} +0 -0
- /package/dist/chunks/{quickjs-runtime-C75KPH77.js.map → quickjs-runtime-6LAQLSB4.js.map} +0 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActorManager,
|
|
3
|
+
AgentManager,
|
|
4
|
+
FabricControlPlane,
|
|
5
|
+
LifecycleBroker,
|
|
6
|
+
MeshStore,
|
|
7
|
+
ParticipantDirectory,
|
|
8
|
+
actorParticipantRecord,
|
|
9
|
+
agentParticipantRecords,
|
|
10
|
+
lifecycleSourceIdentity,
|
|
11
|
+
useBudgetLedger
|
|
12
|
+
} from "./chunk-F2GKEXGI.js";
|
|
13
|
+
import {
|
|
14
|
+
parseFabricOwnedModelGuidance,
|
|
15
|
+
resolveFabricModelGuidance
|
|
16
|
+
} from "./chunk-2YCOXT4W.js";
|
|
17
|
+
import {
|
|
18
|
+
RESIDENT_HOST_FORMAT,
|
|
19
|
+
residentDeliveryPrefix,
|
|
20
|
+
residentHostId
|
|
21
|
+
} from "./chunk-X2MKXKDA.js";
|
|
22
|
+
import {
|
|
23
|
+
writeJsonAtomic
|
|
24
|
+
} from "./chunk-3SGRTVFP.js";
|
|
25
|
+
|
|
26
|
+
// src/residency/host.ts
|
|
27
|
+
import { randomUUID } from "node:crypto";
|
|
28
|
+
import fs from "node:fs";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
var REQUEST_POLL_MS = 50;
|
|
32
|
+
var IDLE_EXIT_MS = 3e4;
|
|
33
|
+
var COMPLETION_MAX_CHARS = 8e3;
|
|
34
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
var atomicWrite = (filePath, value) => {
|
|
36
|
+
writeJsonAtomic(filePath, value, { space: 2 });
|
|
37
|
+
};
|
|
38
|
+
var readJson = (filePath) => {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
41
|
+
} catch {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
46
|
+
var processAlive = (pid) => {
|
|
47
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
48
|
+
try {
|
|
49
|
+
process.kill(pid, 0);
|
|
50
|
+
return true;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var ResidentHostAlreadyRunning = class extends Error {
|
|
56
|
+
};
|
|
57
|
+
var parseResidentHostConfigPath = (argv) => {
|
|
58
|
+
const index = argv.indexOf("--config");
|
|
59
|
+
const value = index >= 0 ? argv[index + 1] : void 0;
|
|
60
|
+
if (!value) throw new Error("Missing resident host argument: --config");
|
|
61
|
+
return path.resolve(value);
|
|
62
|
+
};
|
|
63
|
+
var validateResidentHostConfig = (value, configPath) => {
|
|
64
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
65
|
+
throw new Error("Invalid Fabric resident host config");
|
|
66
|
+
}
|
|
67
|
+
const config = value;
|
|
68
|
+
if (config.format !== RESIDENT_HOST_FORMAT || typeof config.rootId !== "string" || typeof config.sessionId !== "string" || typeof config.cwd !== "string" || typeof config.projectRoot !== "string" || typeof config.meshRoot !== "string" || typeof config.actorRoot !== "string" || typeof config.residencyRoot !== "string" || typeof config.fullCodeMode !== "boolean" || typeof config.agents !== "object" || config.agents === null || typeof config.mesh !== "object" || config.mesh === null || typeof config.retention !== "object" || config.retention === null || typeof config.workerPath !== "string" || typeof config.fabricExtensionPath !== "string" || typeof config.piBinary !== "string" || typeof config.claudeBinary !== "string" || typeof config.vedaBinary !== "string") {
|
|
69
|
+
throw new Error("Fabric resident host config is incomplete");
|
|
70
|
+
}
|
|
71
|
+
if (path.resolve(config.residencyRoot) !== path.dirname(configPath)) {
|
|
72
|
+
throw new Error("Fabric resident host config is outside its residency root");
|
|
73
|
+
}
|
|
74
|
+
if (!config.mesh.enabled) throw new Error("Durable residency requires the Fabric mesh");
|
|
75
|
+
return config;
|
|
76
|
+
};
|
|
77
|
+
var ResidentHost = class {
|
|
78
|
+
constructor(config, onIdle = () => {
|
|
79
|
+
}) {
|
|
80
|
+
this.config = config;
|
|
81
|
+
this.onIdle = onIdle;
|
|
82
|
+
this.hostId = residentHostId(config.rootId);
|
|
83
|
+
this.identity = { id: this.hostId, name: "Fabric resident host", kind: "agent" };
|
|
84
|
+
this.#ownerPath = path.join(config.residencyRoot, "owner.json");
|
|
85
|
+
this.#lockPath = path.join(config.residencyRoot, "host.lock");
|
|
86
|
+
this.#errorPath = path.join(config.residencyRoot, "error.json");
|
|
87
|
+
this.#requestsPath = path.join(config.residencyRoot, "requests");
|
|
88
|
+
this.#processingPath = path.join(config.residencyRoot, "processing");
|
|
89
|
+
this.#responsesPath = path.join(config.residencyRoot, "responses");
|
|
90
|
+
this.#agentsPath = path.join(config.residencyRoot, "agents");
|
|
91
|
+
this.#deliveryPrefix = residentDeliveryPrefix(config.rootId);
|
|
92
|
+
this.mesh = new MeshStore(config.meshRoot, config.mesh.maxEventBytes, config.mesh.maxReadEvents);
|
|
93
|
+
this.participants = new ParticipantDirectory(this.mesh, {
|
|
94
|
+
enabled: true,
|
|
95
|
+
hostId: this.hostId,
|
|
96
|
+
rootId: config.rootId,
|
|
97
|
+
identity: this.identity
|
|
98
|
+
});
|
|
99
|
+
this.control = new FabricControlPlane(this.mesh, this.identity, {
|
|
100
|
+
enabled: true,
|
|
101
|
+
hostId: this.hostId,
|
|
102
|
+
pollMs: config.mesh.actorPollMs
|
|
103
|
+
});
|
|
104
|
+
if (config.agents.budgetUsd > 0) {
|
|
105
|
+
const budgetFile = path.join(config.residencyRoot, "budget.jsonl");
|
|
106
|
+
fs.mkdirSync(path.dirname(budgetFile), { recursive: true, mode: 448 });
|
|
107
|
+
if (!fs.existsSync(budgetFile)) fs.writeFileSync(budgetFile, "", { mode: 384 });
|
|
108
|
+
useBudgetLedger({
|
|
109
|
+
budget: config.agents.budgetUsd,
|
|
110
|
+
file: budgetFile,
|
|
111
|
+
id: this.hostId
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const guidanceConfigPath = path.join(config.residencyRoot, "config.json");
|
|
115
|
+
const currentModelGuidance = () => {
|
|
116
|
+
const current = readJson(guidanceConfigPath);
|
|
117
|
+
return parseFabricOwnedModelGuidance(current?.modelGuidance ?? config.modelGuidance);
|
|
118
|
+
};
|
|
119
|
+
this.agents = new AgentManager(config.cwd, config.agents, {
|
|
120
|
+
workerPath: config.workerPath,
|
|
121
|
+
fabricExtensionPath: config.fabricExtensionPath,
|
|
122
|
+
piBinary: config.piBinary,
|
|
123
|
+
claudeBinary: config.claudeBinary,
|
|
124
|
+
vedaBinary: config.vedaBinary,
|
|
125
|
+
runRoot: path.join(config.residencyRoot, "runs"),
|
|
126
|
+
fullCodeMode: config.fullCodeMode,
|
|
127
|
+
mainAgentId: config.rootId,
|
|
128
|
+
meshRoot: config.meshRoot,
|
|
129
|
+
projectRoot: config.projectRoot,
|
|
130
|
+
hostId: this.hostId,
|
|
131
|
+
identityId: this.identity.id,
|
|
132
|
+
retention: config.retention,
|
|
133
|
+
resolveParticipantGuidance: ({ model }) => {
|
|
134
|
+
if (!model) return void 0;
|
|
135
|
+
return resolveFabricModelGuidance(currentModelGuidance(), {
|
|
136
|
+
model,
|
|
137
|
+
target: "participant",
|
|
138
|
+
includeSlots: false
|
|
139
|
+
}).appendText || void 0;
|
|
140
|
+
},
|
|
141
|
+
onLifecycle: (event) => void this.lifecycle?.publish(event).catch(() => void 0),
|
|
142
|
+
onBackgroundComplete: (result) => {
|
|
143
|
+
if (!config.agents.notifyOnComplete) return;
|
|
144
|
+
const durationMs = Math.max(0, (result.finishedAt ?? Date.now()) - result.startedAt);
|
|
145
|
+
const summary = (result.text || result.error || "no result").slice(0, COMPLETION_MAX_CHARS);
|
|
146
|
+
void this.#queueDelivery(
|
|
147
|
+
{ id: result.id, name: result.name, kind: "agent" },
|
|
148
|
+
`Fabric agent ${result.id.slice(0, 8)} ${result.status} after ${Math.round(durationMs / 1e3)}s: ${summary}`,
|
|
149
|
+
"followUp",
|
|
150
|
+
true,
|
|
151
|
+
result
|
|
152
|
+
).catch(() => void 0);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const canManageActor = (id) => {
|
|
156
|
+
const participant = this.participants.get(id);
|
|
157
|
+
return participant ? participant.ownerHostId === this.hostId : void 0;
|
|
158
|
+
};
|
|
159
|
+
const lineageAlive = (rootId) => this.participants.get(rootId) !== void 0;
|
|
160
|
+
this.actors = new ActorManager(
|
|
161
|
+
config.sessionId,
|
|
162
|
+
this.identity,
|
|
163
|
+
this.mesh,
|
|
164
|
+
config.mesh,
|
|
165
|
+
this.agents,
|
|
166
|
+
({ actor, message, delivery, triggerTurn }) => {
|
|
167
|
+
if (!message.text) return;
|
|
168
|
+
const mode = delivery === "steer" ? "steer" : "followUp";
|
|
169
|
+
const triggers = delivery === "nextTurn" ? false : triggerTurn;
|
|
170
|
+
void this.#queueDelivery(
|
|
171
|
+
{ id: actor.id, name: actor.name, kind: "actor" },
|
|
172
|
+
message.text,
|
|
173
|
+
mode,
|
|
174
|
+
triggers,
|
|
175
|
+
message.data
|
|
176
|
+
).catch(() => void 0);
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
actorRoot: config.actorRoot,
|
|
180
|
+
persistent: true,
|
|
181
|
+
canManageActor,
|
|
182
|
+
lineageAlive,
|
|
183
|
+
claimResidency: "durable",
|
|
184
|
+
rootId: config.rootId,
|
|
185
|
+
meshCursorPath: path.join(config.residencyRoot, "actor-mesh-cursor.json"),
|
|
186
|
+
retention: config.retention
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
this.lifecycle = new LifecycleBroker(
|
|
190
|
+
this.mesh,
|
|
191
|
+
this.identity,
|
|
192
|
+
this.participants,
|
|
193
|
+
{
|
|
194
|
+
enabled: true,
|
|
195
|
+
pollMs: config.mesh.actorPollMs,
|
|
196
|
+
maxReadEvents: config.mesh.maxReadEvents
|
|
197
|
+
},
|
|
198
|
+
(subscription, event) => this.#deliverLifecycle(subscription, event)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
hostId;
|
|
202
|
+
identity;
|
|
203
|
+
mesh;
|
|
204
|
+
participants;
|
|
205
|
+
control;
|
|
206
|
+
agents;
|
|
207
|
+
actors;
|
|
208
|
+
lifecycle;
|
|
209
|
+
#ownerPath;
|
|
210
|
+
#lockPath;
|
|
211
|
+
#errorPath;
|
|
212
|
+
#requestsPath;
|
|
213
|
+
#processingPath;
|
|
214
|
+
#responsesPath;
|
|
215
|
+
#agentsPath;
|
|
216
|
+
#deliveryPrefix;
|
|
217
|
+
#token = randomUUID();
|
|
218
|
+
#requestTimer;
|
|
219
|
+
#pollingRequests = false;
|
|
220
|
+
#closed = false;
|
|
221
|
+
#started = false;
|
|
222
|
+
#idleSince = Date.now();
|
|
223
|
+
async start() {
|
|
224
|
+
if (this.#started) return;
|
|
225
|
+
this.#acquireLock();
|
|
226
|
+
this.#started = true;
|
|
227
|
+
fs.mkdirSync(this.#requestsPath, { recursive: true, mode: 448 });
|
|
228
|
+
fs.mkdirSync(this.#processingPath, { recursive: true, mode: 448 });
|
|
229
|
+
fs.mkdirSync(this.#responsesPath, { recursive: true, mode: 448 });
|
|
230
|
+
fs.mkdirSync(this.#agentsPath, { recursive: true, mode: 448 });
|
|
231
|
+
this.#recoverInterruptedRequests();
|
|
232
|
+
const firstSeenAgents = /* @__PURE__ */ new Map();
|
|
233
|
+
this.participants.registerSource(
|
|
234
|
+
() => agentParticipantRecords(
|
|
235
|
+
this.agents.listForUi(),
|
|
236
|
+
this.config.rootId,
|
|
237
|
+
this.hostId,
|
|
238
|
+
this.identity.id,
|
|
239
|
+
this.config.rootId,
|
|
240
|
+
firstSeenAgents
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
this.participants.registerSource(
|
|
244
|
+
() => this.actors.listOwned().map(
|
|
245
|
+
(actor) => actorParticipantRecord(
|
|
246
|
+
actor,
|
|
247
|
+
this.config.rootId,
|
|
248
|
+
this.hostId,
|
|
249
|
+
this.identity.id,
|
|
250
|
+
this.config.rootId
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
this.agents.subscribeUi(() => this.participants.scheduleRefresh());
|
|
255
|
+
this.actors.subscribe(() => this.participants.scheduleRefresh());
|
|
256
|
+
this.control.start((command, from, signal) => this.#acceptControl(command, from, signal));
|
|
257
|
+
await this.participants.start().catch(() => void 0);
|
|
258
|
+
this.lifecycle.start();
|
|
259
|
+
this.#requestTimer = setInterval(
|
|
260
|
+
() => void this.#pollRequests().catch(() => void 0),
|
|
261
|
+
REQUEST_POLL_MS
|
|
262
|
+
);
|
|
263
|
+
const now = Date.now();
|
|
264
|
+
const owner = {
|
|
265
|
+
format: RESIDENT_HOST_FORMAT,
|
|
266
|
+
hostId: this.hostId,
|
|
267
|
+
pid: process.pid,
|
|
268
|
+
token: this.#token,
|
|
269
|
+
startedAt: now,
|
|
270
|
+
readyAt: now
|
|
271
|
+
};
|
|
272
|
+
atomicWrite(this.#ownerPath, owner);
|
|
273
|
+
fs.rmSync(this.#errorPath, { force: true });
|
|
274
|
+
await this.#pollRequests();
|
|
275
|
+
}
|
|
276
|
+
async close() {
|
|
277
|
+
if (this.#closed) return;
|
|
278
|
+
this.#closed = true;
|
|
279
|
+
if (this.#requestTimer) clearInterval(this.#requestTimer);
|
|
280
|
+
this.#requestTimer = void 0;
|
|
281
|
+
while (this.#pollingRequests) await delay(10);
|
|
282
|
+
await this.participants.quiesce().catch(() => void 0);
|
|
283
|
+
await this.lifecycle.close().catch(() => void 0);
|
|
284
|
+
await this.control.close().catch(() => void 0);
|
|
285
|
+
try {
|
|
286
|
+
await this.actors.close();
|
|
287
|
+
} finally {
|
|
288
|
+
await this.agents.close();
|
|
289
|
+
await this.participants.close().catch(() => void 0);
|
|
290
|
+
this.#releaseLock();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async #acceptControl(command, _from, signal) {
|
|
294
|
+
if (command.operation === "cancel") {
|
|
295
|
+
return { accepted: false, error: "Cancel commands are handled by the control plane" };
|
|
296
|
+
}
|
|
297
|
+
if (command.operation === "stop") {
|
|
298
|
+
try {
|
|
299
|
+
await this.agents.stop(command.targetId);
|
|
300
|
+
this.participants.scheduleRefresh();
|
|
301
|
+
return { accepted: true, messageId: command.commandId };
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (!(error instanceof Error && /Unknown Fabric agent/.test(error.message))) {
|
|
304
|
+
return { accepted: false, error: errorMessage(error) };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
if (!this.actors.owns(command.targetId)) {
|
|
309
|
+
return { accepted: false, error: `Resident host does not own ${command.targetId}` };
|
|
310
|
+
}
|
|
311
|
+
await this.actors.stop(command.targetId);
|
|
312
|
+
this.participants.scheduleRefresh();
|
|
313
|
+
return { accepted: true, messageId: command.commandId };
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return { accepted: false, error: errorMessage(error) };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const message = command.message?.trim();
|
|
319
|
+
if (!message) return { accepted: false, error: "Fabric control message must not be empty" };
|
|
320
|
+
if (command.operation === "ask") {
|
|
321
|
+
try {
|
|
322
|
+
if (!this.actors.owns(command.targetId)) {
|
|
323
|
+
return { accepted: false, error: `Resident host does not own ${command.targetId}` };
|
|
324
|
+
}
|
|
325
|
+
const result = await this.actors.ask(
|
|
326
|
+
command.targetId,
|
|
327
|
+
message,
|
|
328
|
+
command.data,
|
|
329
|
+
signal,
|
|
330
|
+
command.binding !== void 0 ? { binding: command.binding } : {}
|
|
331
|
+
);
|
|
332
|
+
return { accepted: true, messageId: result.id, result };
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return { accepted: false, error: errorMessage(error) };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
this.agents.status(command.targetId);
|
|
339
|
+
const result = command.operation === "steer" ? this.agents.steer(command.targetId, message, command.data) : this.agents.followUp(command.targetId, message, command.data);
|
|
340
|
+
return { accepted: true, messageId: result.messageId };
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (!(error instanceof Error && /Unknown Fabric agent/.test(error.message))) {
|
|
343
|
+
return { accepted: false, error: errorMessage(error) };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
if (!this.actors.owns(command.targetId)) {
|
|
348
|
+
return { accepted: false, error: `Resident host does not own ${command.targetId}` };
|
|
349
|
+
}
|
|
350
|
+
const result = this.actors.tell(
|
|
351
|
+
command.targetId,
|
|
352
|
+
message,
|
|
353
|
+
command.data,
|
|
354
|
+
command.binding !== void 0 ? { binding: command.binding } : {}
|
|
355
|
+
);
|
|
356
|
+
return { accepted: true, messageId: result.messageId };
|
|
357
|
+
} catch (error) {
|
|
358
|
+
return { accepted: false, error: errorMessage(error) };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async #deliverLifecycle(subscription, event) {
|
|
362
|
+
const message = `Fabric lifecycle ${event.event} from ${event.source.name} (${event.source.id})${event.status ? ` with status ${event.status}` : ""}.`;
|
|
363
|
+
if (subscription.to === this.config.rootId) {
|
|
364
|
+
await this.#queueDelivery(
|
|
365
|
+
lifecycleSourceIdentity(event.source),
|
|
366
|
+
message,
|
|
367
|
+
subscription.delivery,
|
|
368
|
+
subscription.triggerTurn,
|
|
369
|
+
event
|
|
370
|
+
);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
this.agents.status(subscription.to);
|
|
375
|
+
if (subscription.delivery === "steer") this.agents.steer(subscription.to, message, event);
|
|
376
|
+
else this.agents.followUp(subscription.to, message, event);
|
|
377
|
+
return;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (!(error instanceof Error && /Unknown Fabric agent/.test(error.message))) throw error;
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
if (this.actors.owns(subscription.to)) {
|
|
383
|
+
this.actors.tell(subscription.to, message, event);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
const target = this.participants.get(subscription.to);
|
|
389
|
+
if (!target) throw new Error(`Unknown Fabric lifecycle target: ${subscription.to}`);
|
|
390
|
+
await this.control.request(
|
|
391
|
+
target.ownerHostId,
|
|
392
|
+
target.id,
|
|
393
|
+
subscription.delivery,
|
|
394
|
+
{ message, data: event, triggerTurn: subscription.triggerTurn },
|
|
395
|
+
target.ownerIdentityId
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
async #queueDelivery(from, message, delivery, triggerTurn, data) {
|
|
399
|
+
const id = randomUUID();
|
|
400
|
+
const record = {
|
|
401
|
+
format: RESIDENT_HOST_FORMAT,
|
|
402
|
+
id,
|
|
403
|
+
rootId: this.config.rootId,
|
|
404
|
+
from,
|
|
405
|
+
delivery,
|
|
406
|
+
triggerTurn,
|
|
407
|
+
message,
|
|
408
|
+
...data === void 0 ? {} : { data },
|
|
409
|
+
createdAt: Date.now()
|
|
410
|
+
};
|
|
411
|
+
try {
|
|
412
|
+
await this.mesh.put({
|
|
413
|
+
key: `${this.#deliveryPrefix}${id}`,
|
|
414
|
+
value: record,
|
|
415
|
+
identity: this.identity,
|
|
416
|
+
ifVersion: 0
|
|
417
|
+
});
|
|
418
|
+
} catch {
|
|
419
|
+
await this.mesh.put({
|
|
420
|
+
key: `${this.#deliveryPrefix}${id}`,
|
|
421
|
+
value: {
|
|
422
|
+
...record,
|
|
423
|
+
message: message.slice(0, Math.max(1, this.config.mesh.eventContextChars)),
|
|
424
|
+
data: { fabricTruncated: true }
|
|
425
|
+
},
|
|
426
|
+
identity: this.identity,
|
|
427
|
+
ifVersion: 0
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async #pollRequests() {
|
|
432
|
+
if (this.#pollingRequests || this.#closed) return;
|
|
433
|
+
this.#pollingRequests = true;
|
|
434
|
+
try {
|
|
435
|
+
let entries;
|
|
436
|
+
try {
|
|
437
|
+
entries = fs.readdirSync(this.#requestsPath).filter((entry) => entry.endsWith(".json"));
|
|
438
|
+
} catch {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
for (const entry of entries.slice(0, 32)) {
|
|
442
|
+
const source = path.join(this.#requestsPath, entry);
|
|
443
|
+
const processing = path.join(this.#processingPath, entry);
|
|
444
|
+
try {
|
|
445
|
+
fs.renameSync(source, processing);
|
|
446
|
+
} catch {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
await this.#processRequest(processing);
|
|
450
|
+
}
|
|
451
|
+
} finally {
|
|
452
|
+
this.#pollingRequests = false;
|
|
453
|
+
this.#checkIdle();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
#checkIdle() {
|
|
457
|
+
const activeActor = this.actors.listOwned().some((actor) => actor.residency === "durable" && actor.status !== "stopped");
|
|
458
|
+
const activeAgent = this.agents.listForUi().some((agent) => agent.status === "queued" || agent.status === "running");
|
|
459
|
+
let pendingRequest = false;
|
|
460
|
+
try {
|
|
461
|
+
pendingRequest = fs.readdirSync(this.#requestsPath).some((entry) => entry.endsWith(".json"));
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
if (activeActor || activeAgent || pendingRequest) {
|
|
465
|
+
this.#idleSince = Date.now();
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (Date.now() - this.#idleSince >= IDLE_EXIT_MS) this.onIdle();
|
|
469
|
+
}
|
|
470
|
+
async #processRequest(filePath) {
|
|
471
|
+
const command = readJson(filePath);
|
|
472
|
+
const requestId = command?.requestId ?? path.basename(filePath, ".json");
|
|
473
|
+
let response;
|
|
474
|
+
try {
|
|
475
|
+
if (command?.format !== RESIDENT_HOST_FORMAT || command.rootId !== this.config.rootId || command.requestId !== requestId) {
|
|
476
|
+
throw new Error("Invalid Fabric residency request");
|
|
477
|
+
}
|
|
478
|
+
if (command.operation === "spawn") {
|
|
479
|
+
if (command.request.sessionSeed || command.request.sessionFile || command.request.actorId || command.request.actorName || command.request.meshRoot || command.request.runnerSessionId || command.request.systemPrompt || command.request.images) {
|
|
480
|
+
throw new Error("Durable agents.spawn accepts only its public task and run settings");
|
|
481
|
+
}
|
|
482
|
+
const handle = await this.agents.spawn({ ...command.request, residency: "durable" });
|
|
483
|
+
this.agents.detachSignal(handle.id);
|
|
484
|
+
const runDirectory = this.agents.runDirectory(handle.id);
|
|
485
|
+
if (!runDirectory) throw new Error(`Resident agent ${handle.id} has no run directory`);
|
|
486
|
+
const worktreeGitRoot = this.agents.worktreeGitRoot(handle.id);
|
|
487
|
+
const metadata = {
|
|
488
|
+
format: RESIDENT_HOST_FORMAT,
|
|
489
|
+
rootId: this.config.rootId,
|
|
490
|
+
id: handle.id,
|
|
491
|
+
runDirectory,
|
|
492
|
+
handle: { ...handle, residency: "durable" },
|
|
493
|
+
...worktreeGitRoot ? { worktreeGitRoot } : {},
|
|
494
|
+
createdAt: Date.now(),
|
|
495
|
+
updatedAt: Date.now()
|
|
496
|
+
};
|
|
497
|
+
atomicWrite(path.join(this.#agentsPath, `${handle.id}.json`), metadata);
|
|
498
|
+
response = {
|
|
499
|
+
format: RESIDENT_HOST_FORMAT,
|
|
500
|
+
requestId,
|
|
501
|
+
ok: true,
|
|
502
|
+
handle: metadata.handle,
|
|
503
|
+
completedAt: Date.now()
|
|
504
|
+
};
|
|
505
|
+
} else if (command.operation === "foreground") {
|
|
506
|
+
this.agents.markForeground(command.id);
|
|
507
|
+
response = {
|
|
508
|
+
format: RESIDENT_HOST_FORMAT,
|
|
509
|
+
requestId,
|
|
510
|
+
ok: true,
|
|
511
|
+
completedAt: Date.now()
|
|
512
|
+
};
|
|
513
|
+
} else if (command.operation === "cleanup") {
|
|
514
|
+
await this.agents.wait(command.id);
|
|
515
|
+
await this.agents.cleanup(command.id, command.deleteBranch);
|
|
516
|
+
fs.rmSync(path.join(this.#agentsPath, `${command.id}.json`), { force: true });
|
|
517
|
+
response = {
|
|
518
|
+
format: RESIDENT_HOST_FORMAT,
|
|
519
|
+
requestId,
|
|
520
|
+
ok: true,
|
|
521
|
+
completedAt: Date.now()
|
|
522
|
+
};
|
|
523
|
+
} else if (command.operation === "createActor") {
|
|
524
|
+
if (command.request.residency !== "durable") {
|
|
525
|
+
throw new Error("Resident host createActor only supports durable residency");
|
|
526
|
+
}
|
|
527
|
+
const actor = await this.actors.create(command.request);
|
|
528
|
+
response = {
|
|
529
|
+
format: RESIDENT_HOST_FORMAT,
|
|
530
|
+
requestId,
|
|
531
|
+
ok: true,
|
|
532
|
+
actor,
|
|
533
|
+
completedAt: Date.now()
|
|
534
|
+
};
|
|
535
|
+
} else {
|
|
536
|
+
if (!this.actors.owns(command.id)) {
|
|
537
|
+
throw new Error(`Resident host does not own ${command.id}`);
|
|
538
|
+
}
|
|
539
|
+
await this.actors.remove(command.id);
|
|
540
|
+
response = {
|
|
541
|
+
format: RESIDENT_HOST_FORMAT,
|
|
542
|
+
requestId,
|
|
543
|
+
ok: true,
|
|
544
|
+
completedAt: Date.now()
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
response = {
|
|
549
|
+
format: RESIDENT_HOST_FORMAT,
|
|
550
|
+
requestId,
|
|
551
|
+
ok: false,
|
|
552
|
+
error: errorMessage(error),
|
|
553
|
+
completedAt: Date.now()
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
atomicWrite(path.join(this.#responsesPath, `${requestId}.json`), response);
|
|
557
|
+
fs.rmSync(filePath, { force: true });
|
|
558
|
+
this.participants.scheduleRefresh();
|
|
559
|
+
}
|
|
560
|
+
#recoverInterruptedRequests() {
|
|
561
|
+
let entries;
|
|
562
|
+
try {
|
|
563
|
+
entries = fs.readdirSync(this.#processingPath).filter((entry) => entry.endsWith(".json"));
|
|
564
|
+
} catch {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
for (const entry of entries) {
|
|
568
|
+
const requestId = path.basename(entry, ".json");
|
|
569
|
+
const response = {
|
|
570
|
+
format: RESIDENT_HOST_FORMAT,
|
|
571
|
+
requestId,
|
|
572
|
+
ok: false,
|
|
573
|
+
error: "Fabric residency outcome is indeterminate after resident host restart",
|
|
574
|
+
completedAt: Date.now()
|
|
575
|
+
};
|
|
576
|
+
atomicWrite(path.join(this.#responsesPath, entry), response);
|
|
577
|
+
fs.rmSync(path.join(this.#processingPath, entry), { force: true });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
#acquireLock() {
|
|
581
|
+
fs.mkdirSync(this.config.residencyRoot, { recursive: true, mode: 448 });
|
|
582
|
+
const existing = readJson(this.#ownerPath);
|
|
583
|
+
if (existing && processAlive(existing.pid)) {
|
|
584
|
+
throw new ResidentHostAlreadyRunning(`Fabric resident host is already running (${existing.pid})`);
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const descriptor = fs.openSync(this.#lockPath, "wx", 384);
|
|
588
|
+
fs.writeFileSync(descriptor, JSON.stringify({ token: this.#token, pid: process.pid }));
|
|
589
|
+
fs.closeSync(descriptor);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
592
|
+
const locked = readJson(this.#lockPath);
|
|
593
|
+
if (typeof locked?.pid === "number" && processAlive(locked.pid)) {
|
|
594
|
+
throw new ResidentHostAlreadyRunning(`Fabric resident host is starting (${locked.pid})`);
|
|
595
|
+
}
|
|
596
|
+
fs.rmSync(this.#lockPath, { force: true });
|
|
597
|
+
const descriptor = fs.openSync(this.#lockPath, "wx", 384);
|
|
598
|
+
fs.writeFileSync(descriptor, JSON.stringify({ token: this.#token, pid: process.pid }));
|
|
599
|
+
fs.closeSync(descriptor);
|
|
600
|
+
} else {
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
#releaseLock() {
|
|
606
|
+
const lock = readJson(this.#lockPath);
|
|
607
|
+
if (lock?.token === this.#token) fs.rmSync(this.#lockPath, { force: true });
|
|
608
|
+
const owner = readJson(this.#ownerPath);
|
|
609
|
+
if (owner?.token === this.#token) fs.rmSync(this.#ownerPath, { force: true });
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
var runResidentHost = async (config, signal) => {
|
|
613
|
+
let finishIdle;
|
|
614
|
+
const idle = new Promise((resolve) => {
|
|
615
|
+
finishIdle = resolve;
|
|
616
|
+
});
|
|
617
|
+
const host = new ResidentHost(config, () => finishIdle?.());
|
|
618
|
+
await host.start();
|
|
619
|
+
if (signal?.aborted) {
|
|
620
|
+
await host.close();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
await Promise.race([
|
|
624
|
+
idle,
|
|
625
|
+
new Promise((resolve) => {
|
|
626
|
+
const finish = () => resolve();
|
|
627
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
628
|
+
process.once("SIGTERM", finish);
|
|
629
|
+
process.once("SIGINT", finish);
|
|
630
|
+
})
|
|
631
|
+
]);
|
|
632
|
+
await host.close();
|
|
633
|
+
};
|
|
634
|
+
var runResidentHostFromConfigPath = async (configPath, signal) => {
|
|
635
|
+
let config;
|
|
636
|
+
try {
|
|
637
|
+
config = validateResidentHostConfig(readJson(configPath), configPath);
|
|
638
|
+
await runResidentHost(config, signal);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
if (error instanceof ResidentHostAlreadyRunning) return;
|
|
641
|
+
const residencyRoot = config?.residencyRoot ?? path.dirname(configPath);
|
|
642
|
+
try {
|
|
643
|
+
atomicWrite(path.join(residencyRoot, "error.json"), {
|
|
644
|
+
error: errorMessage(error),
|
|
645
|
+
occurredAt: Date.now()
|
|
646
|
+
});
|
|
647
|
+
} catch {
|
|
648
|
+
}
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
var isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
653
|
+
if (isMain) {
|
|
654
|
+
const configPath = parseResidentHostConfigPath(process.argv);
|
|
655
|
+
try {
|
|
656
|
+
await runResidentHostFromConfigPath(configPath);
|
|
657
|
+
} catch {
|
|
658
|
+
process.exitCode = 1;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export {
|
|
663
|
+
runResidentHostFromConfigPath
|
|
664
|
+
};
|
|
665
|
+
//# sourceMappingURL=chunk-BT6UELLP.js.map
|