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,405 @@
1
+ export function buildOfficeSnapshot(input) {
2
+ const now = input.now ?? new Date().toISOString();
3
+ const summary = input.project_state?.summary ?? {};
4
+ const roomSettings = input.project_state?.room?.settings ?? {};
5
+ const roomMessages = normalizeMessages(input.project_state?.room?.messages ?? []);
6
+ const recentEvents = normalizeEvents(input.project_state?.recent_events ?? []);
7
+ const tasks = input.tasks.map((task) => normalizeTask(task, input.blocker_threshold));
8
+ const groupedTasks = {
9
+ now: tasks.filter((task) => task.bucket === "now"),
10
+ review: tasks.filter((task) => task.bucket === "review"),
11
+ blocked: tasks.filter((task) => task.bucket === "blocked"),
12
+ ready: tasks.filter((task) => task.bucket === "ready"),
13
+ done: tasks.filter((task) => task.bucket === "done"),
14
+ };
15
+ const artifacts = tasks
16
+ .filter((task) => task.bucket === "done")
17
+ .map((task) => ({
18
+ deliverable_id: task.artifact_ids[0] ?? `deliverable:${task.task_id}`,
19
+ task_id: task.task_id,
20
+ task_title: task.title,
21
+ completed_at: task.updated_at,
22
+ completed_by: task.owner_id,
23
+ summary: task.completion_summary ?? task.summary_text,
24
+ artifact_ids: task.artifact_ids,
25
+ manifest_id: task.current_manifest_id,
26
+ }))
27
+ .sort((left, right) => right.completed_at.localeCompare(left.completed_at));
28
+ const hostCommands = input.project_state?.local_host_commands ?? {};
29
+ const hosts = Object.values(input.project_state?.local_hosts ?? {})
30
+ .map((host) => {
31
+ const normalizedSessions = (host.sessions ?? []).map((session) => ({
32
+ ...session,
33
+ session_id: String(session.session_id ?? ""),
34
+ project_id: session.project_id ? String(session.project_id) : null,
35
+ agent_id: String(session.agent_id ?? ""),
36
+ runner: String(session.runner ?? "codex"),
37
+ mode: String(session.mode ?? "attach"),
38
+ status: String(session.status ?? "running"),
39
+ launched_at: String(session.launched_at ?? now),
40
+ task_id: session.task_id ? String(session.task_id) : null,
41
+ note: session.note ? String(session.note) : null,
42
+ }));
43
+ const commands = (hostCommands[String(host.host_id ?? "")] ?? []).map((command) => normalizeCommand(command));
44
+ const queuedCommands = commands.filter((command) => command.status === "queued" || command.status === "dispatched");
45
+ const online = String(host.status ?? "offline") === "online" && isFreshTimestamp(String(host.last_seen_at ?? now), now, 10000);
46
+ const runningSessions = online ? normalizedSessions.filter((session) => session.status === "running") : [];
47
+ return {
48
+ host_id: String(host.host_id ?? ""),
49
+ display_name: String(host.display_name ?? host.host_id ?? "host"),
50
+ hostname: String(host.hostname ?? host.host_id ?? "host"),
51
+ status: String(host.status ?? "offline"),
52
+ last_seen_at: String(host.last_seen_at ?? now),
53
+ default_workdir: String(host.default_workdir ?? ""),
54
+ online,
55
+ available_runners: {
56
+ codex: Boolean(host.available_runners?.codex),
57
+ claude: Boolean(host.available_runners?.claude),
58
+ },
59
+ running_sessions: runningSessions,
60
+ queued_commands: queuedCommands,
61
+ recent_commands: commands.slice(-8).reverse(),
62
+ active_agents: Array.from(new Set(runningSessions.map((session) => session.agent_id))).sort(),
63
+ environment: host.environment ?? null,
64
+ };
65
+ })
66
+ .sort((left, right) => {
67
+ if (left.online !== right.online) {
68
+ return left.online ? -1 : 1;
69
+ }
70
+ return right.last_seen_at.localeCompare(left.last_seen_at);
71
+ });
72
+ const statusEntries = deriveStatusRail({
73
+ status_feed: input.project_state?.status_feed ?? {},
74
+ tasks,
75
+ });
76
+ const directives = roomMessages
77
+ .filter((message) => message.author_type === "user")
78
+ .slice(-5)
79
+ .reverse()
80
+ .map((message) => ({
81
+ message_id: message.message_id,
82
+ seq: message.seq,
83
+ text: message.text,
84
+ created_at: message.created_at,
85
+ task_id: message.task_id ?? null,
86
+ author_label: message.author_label,
87
+ }));
88
+ const blockers = groupedTasks.blocked;
89
+ const escalatedBlockers = blockers.filter((task) => task.escalated);
90
+ const needsHuman = uniqueTaskCards([
91
+ ...groupedTasks.review,
92
+ ...groupedTasks.blocked.filter((task) => task.escalated),
93
+ ]);
94
+ const focusTask = groupedTasks.now[0] ??
95
+ groupedTasks.review[0] ??
96
+ groupedTasks.blocked[0] ??
97
+ groupedTasks.ready[0] ??
98
+ groupedTasks.done[0] ??
99
+ null;
100
+ const headline = summary.accepted_understanding ??
101
+ focusTask?.accepted_understanding ??
102
+ directives[0]?.text ??
103
+ focusTask?.summary_text ??
104
+ (focusTask ? `Focus on ${focusTask.title}` : "No accepted project brief yet.");
105
+ return {
106
+ meta: {
107
+ project_id: input.project_id,
108
+ generated_at: now,
109
+ blocker_threshold: input.blocker_threshold,
110
+ },
111
+ current_project: {
112
+ project_id: summary.project_id ?? input.project_id,
113
+ name: summary.name ?? input.project_id,
114
+ status: summary.status ?? "active",
115
+ manifest_id: summary.current_manifest_id ?? null,
116
+ manifest_seq: summary.current_manifest_seq ?? null,
117
+ accepted_understanding: summary.accepted_understanding ?? null,
118
+ headline,
119
+ focus_task_id: focusTask?.task_id ?? null,
120
+ focus_task_title: focusTask?.title ?? null,
121
+ metrics: {
122
+ active_tasks: tasks.filter((task) => task.bucket !== "done").length,
123
+ in_progress: groupedTasks.now.length,
124
+ blocked: groupedTasks.blocked.length,
125
+ review: groupedTasks.review.length,
126
+ completed: groupedTasks.done.length,
127
+ deliverables: artifacts.length,
128
+ live_statuses: statusEntries.length,
129
+ online_hosts: hosts.filter((host) => host.online).length,
130
+ running_sessions: hosts.reduce((count, host) => count + host.running_sessions.length, 0),
131
+ },
132
+ },
133
+ task_board: groupedTasks,
134
+ status_rail: statusEntries,
135
+ attention: {
136
+ blockers,
137
+ escalated_blockers: escalatedBlockers,
138
+ review_queue: groupedTasks.review,
139
+ needs_human: needsHuman,
140
+ human_directives: directives,
141
+ },
142
+ artifacts,
143
+ hosts,
144
+ room: {
145
+ settings: {
146
+ conductor_name: String(roomSettings.conductor_name ?? "You"),
147
+ all_agents_listening: Boolean(roomSettings.all_agents_listening ?? true),
148
+ },
149
+ recent_messages: roomMessages.slice(-40),
150
+ recent_decisions: recentEvents.filter((event) => isDecisionEvent(event.type)).slice(0, 12),
151
+ },
152
+ recent_events: recentEvents.slice(0, 24),
153
+ };
154
+ }
155
+ function normalizeTask(task, blockerThreshold) {
156
+ const snapshot = task.snapshot ?? {};
157
+ const events = normalizeTaskEvents(task.events ?? []);
158
+ const latestBlocker = latestEventOfType(events, "blocker.raised");
159
+ const latestCompletion = latestEventOfType(events, "task.completed");
160
+ const latestProgress = latestEventOfTypes(events, [
161
+ "progress.reported",
162
+ "alignment.proposed",
163
+ "human.corrected",
164
+ "human.confirmed",
165
+ "handoff.accepted",
166
+ "claim.granted",
167
+ "understanding.submitted",
168
+ ]);
169
+ const taskId = String(snapshot.task_id ?? "");
170
+ const ownerId = stringOrNull(snapshot.claimed_by_agent_id) ??
171
+ stringOrNull(latestCompletion?.actor_id) ??
172
+ stringOrNull(latestProgress?.actor_id) ??
173
+ null;
174
+ const attemptCount = Number(snapshot.attempt_count ?? 0);
175
+ const blockersOpen = Number(snapshot.blockers_open ?? 0);
176
+ const phase = String(snapshot.phase ?? "new");
177
+ const completionPayload = latestCompletion?.payload ?? {};
178
+ const artifactIds = Array.isArray(completionPayload.artifact_ids)
179
+ ? completionPayload.artifact_ids.map((value) => String(value)).filter(Boolean)
180
+ : [];
181
+ return {
182
+ task_id: taskId,
183
+ title: String((snapshot.title ?? taskId) || "Untitled task"),
184
+ phase,
185
+ bucket: bucketForPhase(phase),
186
+ updated_at: String(snapshot.updated_at ?? task.created_at ?? new Date(0).toISOString()),
187
+ owner_id: ownerId,
188
+ owner_label: ownerId,
189
+ claim_expires_at: stringOrNull(snapshot.claim_expires_at),
190
+ accepted_understanding: stringOrNull(snapshot.accepted_understanding),
191
+ summary_text: summarizeTaskEvent(latestProgress),
192
+ blocker_text: latestBlocker ? String(latestBlocker.payload?.message ?? "Blocked") : null,
193
+ completion_summary: latestCompletion ? String(latestCompletion.payload?.summary ?? "Completed") : null,
194
+ artifact_ids: artifactIds,
195
+ attempt_count: attemptCount,
196
+ blockers_open: blockersOpen,
197
+ escalated: phase === "blocked_or_handoff" && attemptCount >= blockerThreshold,
198
+ needs_human: phase === "awaiting_human_confirmation" || (phase === "blocked_or_handoff" && attemptCount >= blockerThreshold),
199
+ manifest_seq: Number.isFinite(Number(snapshot.manifest_seq)) ? Number(snapshot.manifest_seq) : null,
200
+ task_version: Number.isFinite(Number(snapshot.task_version)) ? Number(snapshot.task_version) : null,
201
+ collaboration_mode: stringOrNull(snapshot.resolved_mode),
202
+ confirmation_required: Boolean(snapshot.confirmation_required),
203
+ current_manifest_id: task.current_manifest_id ? String(task.current_manifest_id) : null,
204
+ };
205
+ }
206
+ function bucketForPhase(phase) {
207
+ if (phase === "executing") {
208
+ return "now";
209
+ }
210
+ if (phase === "awaiting_human_confirmation") {
211
+ return "review";
212
+ }
213
+ if (phase === "blocked_or_handoff") {
214
+ return "blocked";
215
+ }
216
+ if (phase === "completed") {
217
+ return "done";
218
+ }
219
+ return "ready";
220
+ }
221
+ function deriveStatusRail(input) {
222
+ const taskById = new Map(input.tasks.map((task) => [task.task_id, task]));
223
+ const entries = Object.values(input.status_feed)
224
+ .map((entry) => ({
225
+ agent_id: String(entry.agent_id ?? "agent"),
226
+ display_name: String(entry.display_name ?? entry.agent_id ?? "agent"),
227
+ task_id: entry.task_id ? String(entry.task_id) : null,
228
+ task_title: entry.task_id ? taskById.get(String(entry.task_id))?.title ?? null : null,
229
+ state: String(entry.state ?? "active"),
230
+ summary: String(entry.summary ?? "Working"),
231
+ updated_at: String(entry.updated_at ?? new Date(0).toISOString()),
232
+ inferred: false,
233
+ }))
234
+ .sort((left, right) => right.updated_at.localeCompare(left.updated_at));
235
+ const seen = new Set(entries.map((entry) => `${entry.agent_id}:${entry.task_id ?? ""}`));
236
+ for (const task of input.tasks) {
237
+ if (!task.owner_id) {
238
+ continue;
239
+ }
240
+ const key = `${task.owner_id}:${task.task_id}`;
241
+ if (seen.has(key)) {
242
+ continue;
243
+ }
244
+ entries.push({
245
+ agent_id: task.owner_id,
246
+ display_name: task.owner_label ?? task.owner_id,
247
+ task_id: task.task_id,
248
+ task_title: task.title,
249
+ state: task.bucket === "blocked" ? "blocked" : task.bucket === "done" ? "done" : "active",
250
+ summary: task.summary_text ?? task.blocker_text ?? task.completion_summary ?? task.title,
251
+ updated_at: task.updated_at,
252
+ inferred: true,
253
+ });
254
+ seen.add(key);
255
+ }
256
+ return entries.sort((left, right) => right.updated_at.localeCompare(left.updated_at)).slice(0, 18);
257
+ }
258
+ function normalizeMessages(messages) {
259
+ return messages
260
+ .map((message) => ({
261
+ message_id: String(message.message_id ?? `msg_${crypto.randomUUID()}`),
262
+ seq: Number(message.seq ?? 0),
263
+ author_type: String(message.author_type ?? "system"),
264
+ author_id: String(message.author_id ?? "system"),
265
+ author_label: String(message.author_label ?? message.author_id ?? "system"),
266
+ text: String(message.text ?? ""),
267
+ created_at: String(message.created_at ?? new Date(0).toISOString()),
268
+ task_id: message.task_id ? String(message.task_id) : null,
269
+ host_id: message.host_id ? String(message.host_id) : null,
270
+ session_id: message.session_id ? String(message.session_id) : null,
271
+ reply_to_seq: Number.isFinite(Number(message.reply_to_seq)) ? Number(message.reply_to_seq) : null,
272
+ target_host_ids: normalizeStringList(message.target_host_ids),
273
+ target_session_ids: normalizeStringList(message.target_session_ids),
274
+ target_agent_ids: normalizeStringList(message.target_agent_ids),
275
+ }))
276
+ .sort((left, right) => left.seq - right.seq);
277
+ }
278
+ function normalizeEvents(events) {
279
+ return events
280
+ .map((event) => ({
281
+ ts: String(event.ts ?? new Date(0).toISOString()),
282
+ kind: event.kind ?? "sys",
283
+ actor: String(event.actor ?? "system"),
284
+ type: String(event.type ?? "event"),
285
+ detail: event.detail ? String(event.detail) : null,
286
+ payload: event.payload ? String(event.payload) : null,
287
+ }))
288
+ .sort((left, right) => right.ts.localeCompare(left.ts));
289
+ }
290
+ function normalizeTaskEvents(events) {
291
+ return events
292
+ .map((event) => ({
293
+ id: String(event.id ?? `evt_${crypto.randomUUID()}`),
294
+ event_type: String(event.event_type ?? "event"),
295
+ actor_type: String(event.actor_type ?? "system"),
296
+ actor_id: String(event.actor_id ?? "system"),
297
+ payload: event.payload ?? {},
298
+ occurred_at: String(event.occurred_at ?? new Date(0).toISOString()),
299
+ }))
300
+ .sort((left, right) => right.occurred_at.localeCompare(left.occurred_at));
301
+ }
302
+ function latestEventOfType(events, eventType) {
303
+ return events.find((event) => event.event_type === eventType) ?? null;
304
+ }
305
+ function latestEventOfTypes(events, eventTypes) {
306
+ const allowed = new Set(eventTypes);
307
+ return events.find((event) => allowed.has(String(event.event_type))) ?? null;
308
+ }
309
+ function summarizeTaskEvent(event) {
310
+ if (!event) {
311
+ return null;
312
+ }
313
+ const payload = event.payload ?? {};
314
+ switch (event.event_type) {
315
+ case "progress.reported":
316
+ return String(payload.summary ?? "Progress updated");
317
+ case "alignment.proposed":
318
+ return String(payload.proposal_text ?? "Alignment proposed");
319
+ case "human.corrected":
320
+ return String(payload.correction_text ?? "Human requested changes");
321
+ case "human.confirmed":
322
+ return "Human approved execution";
323
+ case "claim.granted":
324
+ return String(payload.approach_summary ?? "Execution claimed");
325
+ case "handoff.accepted":
326
+ return String(payload.reason ?? payload.context_notes ?? "Handoff requested");
327
+ case "understanding.submitted":
328
+ return String(payload.text ?? "Understanding submitted");
329
+ case "task.completed":
330
+ return String(payload.summary ?? "Completed");
331
+ default:
332
+ return null;
333
+ }
334
+ }
335
+ function normalizeCommand(command) {
336
+ return {
337
+ command_id: String(command.command_id ?? ""),
338
+ command_type: String(command.command_type ?? "spawn"),
339
+ runner: command.runner ? String(command.runner) : undefined,
340
+ agent_id: String(command.agent_id ?? ""),
341
+ mode: command.mode ? String(command.mode) : undefined,
342
+ effort: command.effort ? String(command.effort) : null,
343
+ workdir: command.workdir ? String(command.workdir) : undefined,
344
+ task_id: command.task_id ? String(command.task_id) : null,
345
+ session_id: command.session_id ? String(command.session_id) : null,
346
+ prompt: command.prompt ? String(command.prompt) : null,
347
+ target_project_id: command.target_project_id ? String(command.target_project_id) : null,
348
+ target_project_name: command.target_project_name ? String(command.target_project_name) : null,
349
+ status: String(command.status ?? "queued"),
350
+ created_at: String(command.created_at ?? new Date(0).toISOString()),
351
+ updated_at: String(command.updated_at ?? new Date(0).toISOString()),
352
+ result: command.result
353
+ ? {
354
+ session_id: command.result.session_id ? String(command.result.session_id) : undefined,
355
+ note: command.result.note ? String(command.result.note) : null,
356
+ error: command.result.error ? String(command.result.error) : null,
357
+ }
358
+ : undefined,
359
+ };
360
+ }
361
+ function isFreshTimestamp(value, now, maxAgeMs) {
362
+ const ts = Date.parse(value);
363
+ const nowTs = Date.parse(now);
364
+ if (!Number.isFinite(ts) || !Number.isFinite(nowTs)) {
365
+ return false;
366
+ }
367
+ return nowTs - ts <= maxAgeMs;
368
+ }
369
+ function normalizeStringList(values) {
370
+ if (!Array.isArray(values)) {
371
+ return null;
372
+ }
373
+ const next = values.map((value) => String(value)).filter(Boolean);
374
+ return next.length > 0 ? next : null;
375
+ }
376
+ function stringOrNull(value) {
377
+ if (typeof value !== "string") {
378
+ return value == null ? null : String(value);
379
+ }
380
+ return value.trim() ? value : null;
381
+ }
382
+ function isDecisionEvent(eventType) {
383
+ return [
384
+ "task.created",
385
+ "bundle.published",
386
+ "human.confirmed",
387
+ "human.corrected",
388
+ "spawn.requested",
389
+ "spawn.started",
390
+ "spawn.failed",
391
+ "session.removed",
392
+ ].includes(String(eventType ?? ""));
393
+ }
394
+ function uniqueTaskCards(tasks) {
395
+ const seen = new Set();
396
+ const unique = [];
397
+ for (const task of tasks) {
398
+ if (seen.has(task.task_id)) {
399
+ continue;
400
+ }
401
+ seen.add(task.task_id);
402
+ unique.push(task);
403
+ }
404
+ return unique;
405
+ }
@@ -0,0 +1,79 @@
1
+ import { buildOfficeSnapshot } from "./office-view.js";
2
+ export async function fetchProjectState(stub) {
3
+ const stateResponse = await stub.fetch("https://do.internal/state");
4
+ if (!stateResponse.ok) {
5
+ return null;
6
+ }
7
+ return stateResponse.json();
8
+ }
9
+ export async function fetchProjectStatus(stub) {
10
+ const statusResponse = await stub.fetch("https://do.internal/status");
11
+ if (!statusResponse.ok) {
12
+ return [];
13
+ }
14
+ return statusResponse.json();
15
+ }
16
+ export async function fetchProjectTasks(env, stub) {
17
+ const taskRefsResponse = await stub.fetch("https://do.internal/tasks");
18
+ if (!taskRefsResponse.ok) {
19
+ return [];
20
+ }
21
+ const taskRefs = await taskRefsResponse.json();
22
+ const tasks = await Promise.all((taskRefs ?? []).map(async (task) => {
23
+ const taskStub = env.TASKS.get(env.TASKS.idFromName(task.task_id));
24
+ const response = await taskStub.fetch("https://do.internal/snapshot");
25
+ if (!response.ok) {
26
+ return null;
27
+ }
28
+ return response.json();
29
+ }));
30
+ return tasks
31
+ .filter((task) => Boolean(task))
32
+ .sort((left, right) => {
33
+ const leftUpdated = left?.snapshot?.updated_at ?? left?.updated_at ?? "";
34
+ const rightUpdated = right?.snapshot?.updated_at ?? right?.updated_at ?? "";
35
+ return rightUpdated.localeCompare(leftUpdated);
36
+ });
37
+ }
38
+ export function buildProjectPayload(input) {
39
+ const blockerThreshold = normalizeBlockerThreshold(input.env.BLOCKER_THRESHOLD);
40
+ const office = buildOfficeSnapshot({
41
+ project_id: input.project_id,
42
+ project_state: input.project_state,
43
+ tasks: input.tasks,
44
+ blocker_threshold: blockerThreshold,
45
+ now: input.now,
46
+ });
47
+ const summary = input.project_state?.summary ?? null;
48
+ return {
49
+ ...summary,
50
+ id: summary?.project_id ?? input.project_id,
51
+ name: summary?.name ?? input.project_id,
52
+ status: summary?.status ?? "active",
53
+ current_manifest_id: summary?.current_manifest_id ?? null,
54
+ current_manifest_seq: summary?.current_manifest_seq ?? null,
55
+ accepted_understanding: summary?.accepted_understanding ?? null,
56
+ workspace: {
57
+ office_name: "Current Project",
58
+ guild_name: "local-office",
59
+ },
60
+ active_task_count: input.tasks.filter((task) => task?.snapshot?.phase !== "completed").length,
61
+ tasks: input.tasks,
62
+ local_hosts: Object.values(input.project_state?.local_hosts ?? {}),
63
+ local_host_commands: input.project_state?.local_host_commands ?? {},
64
+ room: input.project_state?.room ?? {
65
+ settings: { all_agents_listening: true, conductor_name: "You" },
66
+ next_seq: 0,
67
+ messages: [],
68
+ },
69
+ events: input.project_state?.recent_events ?? [],
70
+ office,
71
+ };
72
+ }
73
+ export function normalizeBlockerThreshold(value) {
74
+ const numeric = Number(value ?? 2);
75
+ if (!Number.isFinite(numeric) || numeric <= 0) {
76
+ return 2;
77
+ }
78
+ return Math.max(1, Math.floor(numeric));
79
+ }
@@ -0,0 +1,9 @@
1
+ export async function handleAgentsBootstrap(request, env) {
2
+ const body = await request.json();
3
+ const id = env.AGENTS.idFromName(body.agent_id);
4
+ return env.AGENTS.get(id).fetch("https://do.internal/bootstrap", {
5
+ method: "POST",
6
+ headers: { "content-type": "application/json" },
7
+ body: JSON.stringify(body),
8
+ });
9
+ }
@@ -0,0 +1,12 @@
1
+ import { requireSessionClaims, readBearerToken } from "../auth/session-token.js";
2
+ export async function handleAgentsDescriptor(request, env) {
3
+ const result = requireSessionClaims(request);
4
+ if (result instanceof Response) {
5
+ return result;
6
+ }
7
+ const id = env.AGENTS.idFromName(result.agent_id);
8
+ return env.AGENTS.get(id).fetch("https://do.internal/descriptor", {
9
+ method: "GET",
10
+ headers: { authorization: `Bearer ${readBearerToken(request)}` },
11
+ });
12
+ }
@@ -0,0 +1,17 @@
1
+ import { requireSessionClaims, readBearerToken } from "../auth/session-token.js";
2
+ export async function handleAgentsEvents(request, env) {
3
+ const result = requireSessionClaims(request);
4
+ if (result instanceof Response) {
5
+ return result;
6
+ }
7
+ const body = await request.json();
8
+ const id = env.AGENTS.idFromName(result.agent_id);
9
+ return env.AGENTS.get(id).fetch("https://do.internal/events", {
10
+ method: "POST",
11
+ headers: {
12
+ authorization: `Bearer ${readBearerToken(request)}`,
13
+ "content-type": "application/json",
14
+ },
15
+ body: JSON.stringify(body),
16
+ });
17
+ }
@@ -0,0 +1,21 @@
1
+ import { requireSessionClaims, readBearerToken } from "../auth/session-token.js";
2
+ export async function handleAgentsHeartbeat(request, env) {
3
+ const result = requireSessionClaims(request);
4
+ if (result instanceof Response) {
5
+ return result;
6
+ }
7
+ const body = await request.json();
8
+ const id = env.AGENTS.idFromName(result.agent_id);
9
+ return env.AGENTS.get(id).fetch("https://do.internal/heartbeat", {
10
+ method: "POST",
11
+ headers: {
12
+ authorization: `Bearer ${readBearerToken(request)}`,
13
+ "content-type": "application/json",
14
+ },
15
+ body: JSON.stringify({
16
+ session_id: result.session_id,
17
+ session_epoch: result.session_epoch,
18
+ ...body,
19
+ }),
20
+ });
21
+ }
@@ -0,0 +1,17 @@
1
+ import { requireSessionClaims, readBearerToken } from "../auth/session-token.js";
2
+ export async function handleAgentsTaskContext(request, env) {
3
+ const result = requireSessionClaims(request);
4
+ if (result instanceof Response) {
5
+ return result;
6
+ }
7
+ const body = await request.json();
8
+ const id = env.AGENTS.idFromName(result.agent_id);
9
+ return env.AGENTS.get(id).fetch("https://do.internal/task-context", {
10
+ method: "POST",
11
+ headers: {
12
+ authorization: `Bearer ${readBearerToken(request)}`,
13
+ "content-type": "application/json",
14
+ },
15
+ body: JSON.stringify(body),
16
+ });
17
+ }