office-core 0.1.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.
Files changed (40) hide show
  1. package/.runtime-dist/scripts/bundle-host-package.js +46 -0
  2. package/.runtime-dist/scripts/demo-multi-agent.js +130 -0
  3. package/.runtime-dist/scripts/home-agent-host.js +1403 -0
  4. package/.runtime-dist/scripts/host-doctor.js +28 -0
  5. package/.runtime-dist/scripts/host-login.js +32 -0
  6. package/.runtime-dist/scripts/host-menu.js +227 -0
  7. package/.runtime-dist/scripts/host-open.js +20 -0
  8. package/.runtime-dist/scripts/install-host.js +108 -0
  9. package/.runtime-dist/scripts/lib/host-config.js +171 -0
  10. package/.runtime-dist/scripts/lib/local-runner.js +287 -0
  11. package/.runtime-dist/scripts/office-cli.js +698 -0
  12. package/.runtime-dist/scripts/run-local-project.js +277 -0
  13. package/.runtime-dist/src/auth/session-token.js +62 -0
  14. package/.runtime-dist/src/discord/outbox-ledger.js +56 -0
  15. package/.runtime-dist/src/do/AgentDO.js +205 -0
  16. package/.runtime-dist/src/do/GatewayShardDO.js +9 -0
  17. package/.runtime-dist/src/do/ProjectDO.js +829 -0
  18. package/.runtime-dist/src/do/TaskDO.js +356 -0
  19. package/.runtime-dist/src/index.js +123 -0
  20. package/.runtime-dist/src/project/office-view.js +405 -0
  21. package/.runtime-dist/src/project/read-model.js +79 -0
  22. package/.runtime-dist/src/routes/agents-bootstrap.js +9 -0
  23. package/.runtime-dist/src/routes/agents-descriptor.js +12 -0
  24. package/.runtime-dist/src/routes/agents-events.js +17 -0
  25. package/.runtime-dist/src/routes/agents-heartbeat.js +21 -0
  26. package/.runtime-dist/src/routes/agents-task-context.js +17 -0
  27. package/.runtime-dist/src/routes/bundles.js +198 -0
  28. package/.runtime-dist/src/routes/local-host.js +49 -0
  29. package/.runtime-dist/src/routes/projects.js +119 -0
  30. package/.runtime-dist/src/routes/tasks.js +67 -0
  31. package/.runtime-dist/src/task/reducer.js +464 -0
  32. package/.runtime-dist/src/types/project.js +1 -0
  33. package/.runtime-dist/src/types/protocol.js +3 -0
  34. package/.runtime-dist/src/types/runtime.js +1 -0
  35. package/README.md +148 -0
  36. package/bin/double-penetration-host.mjs +83 -0
  37. package/package.json +48 -0
  38. package/public/index.html +1581 -0
  39. package/public/install-host.ps1 +64 -0
  40. package/scripts/run-runtime-script.mjs +43 -0
@@ -0,0 +1,356 @@
1
+ import { applyAlarm, applyHumanConfirmation, applyTaskCommand, createTaskState, scheduleAlarm, } from "../task/reducer.js";
2
+ export class TaskDO {
3
+ state;
4
+ env;
5
+ stateCache;
6
+ metaCache;
7
+ constructor(state, env) {
8
+ this.state = state;
9
+ this.env = env;
10
+ }
11
+ async fetch(request) {
12
+ const url = new URL(request.url);
13
+ if (request.method === "GET" && url.pathname === "/snapshot") {
14
+ return Response.json(await this.buildSnapshotView());
15
+ }
16
+ if (request.method === "POST" && url.pathname === "/create-from-discord") {
17
+ const body = await request.json();
18
+ return Response.json(await this.createFromDiscord(body));
19
+ }
20
+ if (request.method === "POST" && url.pathname === "/command") {
21
+ const body = await request.json();
22
+ const current = await this.requireState();
23
+ const meta = await this.requireMeta();
24
+ const currentEventId = current.events.at(-1)?.id ?? null;
25
+ const result = applyTaskCommand(current, body.command, {
26
+ now: body.command.sent_at,
27
+ current_fence: body.fence,
28
+ claim_expiry_sec: body.claim_expiry_sec,
29
+ });
30
+ let nextState = result.state;
31
+ if (result.response.accepted && result.side_effects.some((effect) => effect.kind === "manifest_rebuild")) {
32
+ nextState = this.publishManifest(nextState);
33
+ }
34
+ await this.persistState(nextState);
35
+ await this.applySideEffects(nextState, meta, result.side_effects);
36
+ const latestEvent = nextState.events.at(-1);
37
+ const taskChanged = nextState.snapshot.task_version !== current.snapshot.task_version ||
38
+ nextState.snapshot.manifest_seq !== current.snapshot.manifest_seq ||
39
+ nextState.events.length !== current.events.length;
40
+ if (taskChanged) {
41
+ await this.syncProjectSnapshot(nextState, meta);
42
+ if (latestEvent && latestEvent.id !== currentEventId) {
43
+ await this.appendProjectEvent(nextState.snapshot.project_id, buildProjectEvent(latestEvent));
44
+ }
45
+ }
46
+ await this.rearmAlarm(nextState.snapshot);
47
+ return Response.json({
48
+ ...result.response,
49
+ current_bundle_seq: nextState.snapshot.manifest_seq ?? undefined,
50
+ });
51
+ }
52
+ if (request.method === "POST" && url.pathname === "/human-confirm") {
53
+ const body = await request.json();
54
+ const current = await this.requireState();
55
+ const meta = await this.requireMeta();
56
+ let next = applyHumanConfirmation(current, {
57
+ actor_id: body.actor_id,
58
+ type: body.type,
59
+ correction_text: body.correction_text,
60
+ now: body.now ?? new Date().toISOString(),
61
+ });
62
+ next = this.publishManifest(next);
63
+ await this.persistState(next);
64
+ await this.syncProjectSnapshot(next, meta);
65
+ const latestEvent = next.events.at(-1);
66
+ if (latestEvent) {
67
+ await this.appendProjectEvent(next.snapshot.project_id, buildProjectEvent(latestEvent));
68
+ }
69
+ await this.rearmAlarm(next.snapshot);
70
+ return Response.json(await this.buildSnapshotView());
71
+ }
72
+ if (request.method === "POST" && url.pathname === "/alarm-run") {
73
+ await this.alarm();
74
+ return Response.json(await this.buildSnapshotView());
75
+ }
76
+ return new Response("Not found", { status: 404 });
77
+ }
78
+ async createFromDiscord(input) {
79
+ const now = new Date().toISOString();
80
+ const task_id = input.task_id ?? `tsk_${crypto.randomUUID()}`;
81
+ const discord_thread_id = input.thread_id ?? input.discord_thread_id ?? `th_${crypto.randomUUID()}`;
82
+ const collaboration_policy = input.collaboration_policy ?? "multi_agent";
83
+ const nextState = createTaskState({
84
+ task_id,
85
+ project_id: input.project_id,
86
+ title: input.title,
87
+ now,
88
+ phase: "collecting_understandings",
89
+ confirmation_required: input.confirmation_required ?? false,
90
+ manifest_seq: 1,
91
+ });
92
+ nextState.snapshot.resolved_mode = resolveCollaborationMode(collaboration_policy);
93
+ const meta = {
94
+ collaboration_policy,
95
+ discord_thread_id,
96
+ raw_content: input.raw_content,
97
+ requested_by_discord_user_id: input.requested_by_discord_user_id,
98
+ created_at: now,
99
+ };
100
+ await this.persistMeta(meta);
101
+ await this.persistState(nextState);
102
+ await this.syncProjectSnapshot(nextState, meta);
103
+ await this.appendProjectEvent(input.project_id, {
104
+ ts: now,
105
+ kind: "human",
106
+ actor: input.requested_by_discord_user_id,
107
+ type: "task.created",
108
+ detail: `${task_id} \"${input.title}\"`,
109
+ payload: JSON.stringify({
110
+ collaboration_policy,
111
+ confirmation_required: nextState.snapshot.confirmation_required,
112
+ }),
113
+ });
114
+ return {
115
+ task_id,
116
+ task_version: nextState.snapshot.task_version,
117
+ discord_thread_id,
118
+ };
119
+ }
120
+ async alarm() {
121
+ const current = await this.loadState();
122
+ if (!current) {
123
+ return;
124
+ }
125
+ const meta = await this.requireMeta();
126
+ const result = applyAlarm(current, new Date().toISOString());
127
+ if (!result.expired_claim) {
128
+ await this.rearmAlarm(current.snapshot);
129
+ return;
130
+ }
131
+ await this.persistState(result.state);
132
+ await this.syncProjectSnapshot(result.state, meta);
133
+ const latestEvent = result.state.events.at(-1);
134
+ if (latestEvent) {
135
+ await this.appendProjectEvent(result.state.snapshot.project_id, buildProjectEvent(latestEvent));
136
+ }
137
+ await this.rearmAlarm(result.state.snapshot);
138
+ }
139
+ async buildSnapshotView() {
140
+ const state = await this.loadState();
141
+ if (!state) {
142
+ return null;
143
+ }
144
+ const meta = await this.requireMeta();
145
+ return {
146
+ ...state,
147
+ ...meta,
148
+ current_manifest_id: buildManifestId(state.snapshot.task_id, state.snapshot.manifest_seq),
149
+ current_manifest_seq: state.snapshot.manifest_seq,
150
+ };
151
+ }
152
+ async applySideEffects(state, meta, effects) {
153
+ for (const effect of effects) {
154
+ if (effect.kind === "status_upsert") {
155
+ const projectId = this.env.PROJECTS.idFromName(state.snapshot.project_id);
156
+ await this.env.PROJECTS.get(projectId).fetch(`https://do.internal/status/upsert?project_id=${encodeURIComponent(state.snapshot.project_id)}`, {
157
+ method: "POST",
158
+ headers: { "content-type": "application/json" },
159
+ body: JSON.stringify({
160
+ project_id: state.snapshot.project_id,
161
+ agent_id: effect.data.agent_id,
162
+ display_name: String(effect.data.agent_id ?? ""),
163
+ task_id: effect.data.task_id,
164
+ state: effect.data.state,
165
+ summary: effect.data.summary,
166
+ updated_at: state.snapshot.updated_at,
167
+ }),
168
+ });
169
+ continue;
170
+ }
171
+ if (effect.kind === "manifest_rebuild") {
172
+ await this.appendProjectEvent(state.snapshot.project_id, {
173
+ ts: state.snapshot.updated_at,
174
+ kind: "evt",
175
+ actor: "system",
176
+ type: "bundle.published",
177
+ detail: `${state.snapshot.task_id} → ${buildManifestId(state.snapshot.task_id, state.snapshot.manifest_seq)}`,
178
+ payload: JSON.stringify({
179
+ task_version: state.snapshot.task_version,
180
+ manifest_seq: state.snapshot.manifest_seq,
181
+ policy: meta.collaboration_policy,
182
+ }),
183
+ });
184
+ }
185
+ }
186
+ }
187
+ async syncProjectSnapshot(state, meta) {
188
+ const projectId = this.env.PROJECTS.idFromName(state.snapshot.project_id);
189
+ const stub = this.env.PROJECTS.get(projectId);
190
+ const headers = { "content-type": "application/json" };
191
+ await Promise.all([
192
+ stub.fetch("https://do.internal/tasks/upsert", {
193
+ method: "POST",
194
+ headers,
195
+ body: JSON.stringify({
196
+ project_id: state.snapshot.project_id,
197
+ task_id: state.snapshot.task_id,
198
+ title: state.snapshot.title,
199
+ status: state.snapshot.phase,
200
+ updated_at: state.snapshot.updated_at,
201
+ }),
202
+ }),
203
+ stub.fetch("https://do.internal/manifest", {
204
+ method: "POST",
205
+ headers,
206
+ body: JSON.stringify({
207
+ project_id: state.snapshot.project_id,
208
+ manifest_id: buildManifestId(state.snapshot.task_id, state.snapshot.manifest_seq),
209
+ manifest_seq: state.snapshot.manifest_seq,
210
+ }),
211
+ }),
212
+ stub.fetch("https://do.internal/understanding", {
213
+ method: "POST",
214
+ headers,
215
+ body: JSON.stringify({
216
+ project_id: state.snapshot.project_id,
217
+ accepted_understanding: state.snapshot.accepted_understanding,
218
+ }),
219
+ }),
220
+ ]);
221
+ }
222
+ publishManifest(state) {
223
+ const currentSeq = state.snapshot.manifest_seq ?? 0;
224
+ return {
225
+ ...state,
226
+ snapshot: {
227
+ ...state.snapshot,
228
+ manifest_seq: currentSeq + 1,
229
+ },
230
+ };
231
+ }
232
+ async appendProjectEvent(project_id, event) {
233
+ const projectId = this.env.PROJECTS.idFromName(project_id);
234
+ await this.env.PROJECTS.get(projectId).fetch("https://do.internal/events/add", {
235
+ method: "POST",
236
+ headers: { "content-type": "application/json" },
237
+ body: JSON.stringify({
238
+ project_id,
239
+ ...event,
240
+ }),
241
+ });
242
+ }
243
+ async loadState() {
244
+ if (this.stateCache) {
245
+ return this.stateCache;
246
+ }
247
+ const stored = await this.state.storage.get("task-state");
248
+ this.stateCache = stored ?? undefined;
249
+ return stored ?? null;
250
+ }
251
+ async requireState() {
252
+ const state = await this.loadState();
253
+ if (!state) {
254
+ throw new Error("Task state not initialized");
255
+ }
256
+ return state;
257
+ }
258
+ async persistState(next) {
259
+ this.stateCache = next;
260
+ await this.state.storage.put("task-state", next);
261
+ }
262
+ async loadMeta() {
263
+ if (this.metaCache) {
264
+ return this.metaCache;
265
+ }
266
+ const stored = await this.state.storage.get("task-meta");
267
+ this.metaCache = stored ?? undefined;
268
+ return stored ?? null;
269
+ }
270
+ async requireMeta() {
271
+ const meta = await this.loadMeta();
272
+ if (!meta) {
273
+ throw new Error("Task metadata not initialized");
274
+ }
275
+ return meta;
276
+ }
277
+ async persistMeta(meta) {
278
+ this.metaCache = meta;
279
+ await this.state.storage.put("task-meta", meta);
280
+ }
281
+ async rearmAlarm(snapshot) {
282
+ const due = scheduleAlarm(snapshot);
283
+ if (!due) {
284
+ await this.state.storage.deleteAlarm();
285
+ return;
286
+ }
287
+ await this.state.storage.setAlarm(Date.parse(due));
288
+ }
289
+ }
290
+ function resolveCollaborationMode(policy) {
291
+ switch (policy) {
292
+ case "solo":
293
+ return "solo_visible";
294
+ case "broadcast":
295
+ return "consensus_required";
296
+ default:
297
+ return "parallel_interpretation";
298
+ }
299
+ }
300
+ function buildManifestId(task_id, manifest_seq) {
301
+ if (manifest_seq == null) {
302
+ return null;
303
+ }
304
+ return `mf_${task_id}_${manifest_seq}`;
305
+ }
306
+ function buildProjectEvent(event) {
307
+ const payload = JSON.stringify(event.payload);
308
+ switch (event.event_type) {
309
+ case "human.confirmed":
310
+ case "human.corrected":
311
+ return {
312
+ ts: event.occurred_at,
313
+ kind: "human",
314
+ actor: event.actor_id,
315
+ type: event.event_type,
316
+ detail: event.event_type === "human.corrected" ? "Human requested revision" : "Human approved the current interpretation",
317
+ payload,
318
+ };
319
+ case "claim.expired":
320
+ return {
321
+ ts: event.occurred_at,
322
+ kind: "sys",
323
+ actor: event.actor_id,
324
+ type: event.event_type,
325
+ detail: "Execution claim expired",
326
+ payload,
327
+ };
328
+ case "progress.reported":
329
+ return {
330
+ ts: event.occurred_at,
331
+ kind: "cmd",
332
+ actor: event.actor_id,
333
+ type: event.event_type,
334
+ detail: String(event.payload.summary ?? "Progress updated"),
335
+ payload,
336
+ };
337
+ case "understanding.submitted":
338
+ return {
339
+ ts: event.occurred_at,
340
+ kind: "cmd",
341
+ actor: event.actor_id,
342
+ type: event.event_type,
343
+ detail: "Submitted task understanding",
344
+ payload,
345
+ };
346
+ default:
347
+ return {
348
+ ts: event.occurred_at,
349
+ kind: event.actor_type === "system" ? "sys" : "cmd",
350
+ actor: event.actor_id,
351
+ type: event.event_type,
352
+ detail: null,
353
+ payload,
354
+ };
355
+ }
356
+ }
@@ -0,0 +1,123 @@
1
+ import { AgentDO } from "./do/AgentDO.js";
2
+ import { GatewayShardDO } from "./do/GatewayShardDO.js";
3
+ import { ProjectDO } from "./do/ProjectDO.js";
4
+ import { TaskDO } from "./do/TaskDO.js";
5
+ import { handleAgentsBootstrap } from "./routes/agents-bootstrap.js";
6
+ import { handleAgentsDescriptor } from "./routes/agents-descriptor.js";
7
+ import { handleAgentsEvents } from "./routes/agents-events.js";
8
+ import { handleAgentsHeartbeat } from "./routes/agents-heartbeat.js";
9
+ import { handleAgentsTaskContext } from "./routes/agents-task-context.js";
10
+ import { handleBlobRead, handleManifestRead } from "./routes/bundles.js";
11
+ import { handleLocalHostCommandComplete, handleLocalHostCommandPull, handleLocalHostCommandQueue, handleLocalHostHeartbeat, handleLocalHostProjectSwitch, handleLocalHostRegister, handleLocalHostRoomPull, handleLocalHostSessionRemove, } from "./routes/local-host.js";
12
+ import { handleProjectCreate, handleProjectRead, handleProjectRoomMessageWrite, handleProjectRoomRead, handleProjectRoomSettingsWrite, } from "./routes/projects.js";
13
+ import { handleTaskCreate, handleTaskHumanConfirm, handleTaskRead } from "./routes/tasks.js";
14
+ export { AgentDO, GatewayShardDO, ProjectDO, TaskDO };
15
+ export default {
16
+ async fetch(request, env) {
17
+ try {
18
+ const url = new URL(request.url);
19
+ // Serve dashboard for root — fall through to Workers Assets
20
+ if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
21
+ return env.ASSETS.fetch(new Request(new URL("/index.html", request.url), request));
22
+ }
23
+ if (request.method === "GET" && url.pathname === "/i") {
24
+ return env.ASSETS.fetch(new Request(new URL("/install-host.ps1", request.url), request));
25
+ }
26
+ if (request.method === "GET" && url.pathname === "/healthz") {
27
+ return Response.json({ ok: true });
28
+ }
29
+ if (request.method === "POST" && url.pathname === "/api/agents/bootstrap") {
30
+ return handleAgentsBootstrap(request, env);
31
+ }
32
+ if (request.method === "GET" && url.pathname === "/api/agents/descriptor") {
33
+ return handleAgentsDescriptor(request, env);
34
+ }
35
+ if (request.method === "POST" && url.pathname === "/api/agents/heartbeat") {
36
+ return handleAgentsHeartbeat(request, env);
37
+ }
38
+ if (request.method === "POST" && url.pathname === "/api/agents/events") {
39
+ return handleAgentsEvents(request, env);
40
+ }
41
+ if (request.method === "POST" && url.pathname === "/api/agents/task-context") {
42
+ return handleAgentsTaskContext(request, env);
43
+ }
44
+ if (request.method === "GET" && url.pathname.startsWith("/api/bundles/manifests/")) {
45
+ return handleManifestRead(request, env, url.pathname.split("/").at(-1) ?? "");
46
+ }
47
+ if (request.method === "GET" && url.pathname.startsWith("/api/bundles/blobs/")) {
48
+ return handleBlobRead(request, env, decodeURIComponent(url.pathname.split("/").at(-1) ?? ""));
49
+ }
50
+ if (request.method === "GET" && url.pathname.endsWith("/room")) {
51
+ return handleProjectRoomRead(request, env);
52
+ }
53
+ if (request.method === "GET" && url.pathname.endsWith("/room/messages")) {
54
+ return handleProjectRoomRead(request, env);
55
+ }
56
+ if (request.method === "POST" && (url.pathname.endsWith("/room/messages") || url.pathname.endsWith("/room/messages/upsert"))) {
57
+ return handleProjectRoomMessageWrite(request, env);
58
+ }
59
+ if (request.method === "POST" && url.pathname.endsWith("/room/settings")) {
60
+ return handleProjectRoomSettingsWrite(request, env);
61
+ }
62
+ if (request.method === "POST" && url.pathname === "/api/projects") {
63
+ return handleProjectCreate(request, env);
64
+ }
65
+ if (request.method === "GET" && url.pathname.startsWith("/api/projects/")) {
66
+ return handleProjectRead(request, env);
67
+ }
68
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/register")) {
69
+ return handleLocalHostRegister(request, env);
70
+ }
71
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/heartbeat")) {
72
+ return handleLocalHostHeartbeat(request, env);
73
+ }
74
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/commands/queue")) {
75
+ return handleLocalHostCommandQueue(request, env);
76
+ }
77
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/commands/pull")) {
78
+ return handleLocalHostCommandPull(request, env);
79
+ }
80
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/room/pull")) {
81
+ return handleLocalHostRoomPull(request, env);
82
+ }
83
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/commands/complete")) {
84
+ return handleLocalHostCommandComplete(request, env);
85
+ }
86
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/sessions/remove")) {
87
+ return handleLocalHostSessionRemove(request, env);
88
+ }
89
+ if (request.method === "POST" && url.pathname.endsWith("/local-host/switch")) {
90
+ return handleLocalHostProjectSwitch(request, env);
91
+ }
92
+ if (request.method === "POST" && url.pathname === "/api/tasks") {
93
+ return handleTaskCreate(request, env);
94
+ }
95
+ if (request.method === "POST" && url.pathname.endsWith("/human-confirm")) {
96
+ return handleTaskHumanConfirm(request, env);
97
+ }
98
+ if (request.method === "GET" && url.pathname.startsWith("/api/tasks/")) {
99
+ return handleTaskRead(request, env);
100
+ }
101
+ if (url.pathname === "/do/task") {
102
+ const id = env.TASKS.idFromName(url.searchParams.get("task_id") ?? "tsk_demo");
103
+ return env.TASKS.get(id).fetch(request);
104
+ }
105
+ if (url.pathname === "/do/project") {
106
+ const id = env.PROJECTS.idFromName(url.searchParams.get("project_id") ?? "prj_demo");
107
+ return env.PROJECTS.get(id).fetch(request);
108
+ }
109
+ if (url.pathname === "/do/agent") {
110
+ const agentId = url.searchParams.get("agent_id") ?? "agent_demo";
111
+ const id = env.AGENTS.idFromName(agentId);
112
+ return env.AGENTS.get(id).fetch(request);
113
+ }
114
+ return new Response("Not found", { status: 404 });
115
+ }
116
+ catch (error) {
117
+ if (error instanceof Response) {
118
+ return error;
119
+ }
120
+ throw error;
121
+ }
122
+ },
123
+ };