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.
- package/.runtime-dist/scripts/bundle-host-package.js +46 -0
- package/.runtime-dist/scripts/demo-multi-agent.js +130 -0
- package/.runtime-dist/scripts/home-agent-host.js +1403 -0
- package/.runtime-dist/scripts/host-doctor.js +28 -0
- package/.runtime-dist/scripts/host-login.js +32 -0
- package/.runtime-dist/scripts/host-menu.js +227 -0
- package/.runtime-dist/scripts/host-open.js +20 -0
- package/.runtime-dist/scripts/install-host.js +108 -0
- package/.runtime-dist/scripts/lib/host-config.js +171 -0
- package/.runtime-dist/scripts/lib/local-runner.js +287 -0
- package/.runtime-dist/scripts/office-cli.js +698 -0
- package/.runtime-dist/scripts/run-local-project.js +277 -0
- package/.runtime-dist/src/auth/session-token.js +62 -0
- package/.runtime-dist/src/discord/outbox-ledger.js +56 -0
- package/.runtime-dist/src/do/AgentDO.js +205 -0
- package/.runtime-dist/src/do/GatewayShardDO.js +9 -0
- package/.runtime-dist/src/do/ProjectDO.js +829 -0
- package/.runtime-dist/src/do/TaskDO.js +356 -0
- package/.runtime-dist/src/index.js +123 -0
- package/.runtime-dist/src/project/office-view.js +405 -0
- package/.runtime-dist/src/project/read-model.js +79 -0
- package/.runtime-dist/src/routes/agents-bootstrap.js +9 -0
- package/.runtime-dist/src/routes/agents-descriptor.js +12 -0
- package/.runtime-dist/src/routes/agents-events.js +17 -0
- package/.runtime-dist/src/routes/agents-heartbeat.js +21 -0
- package/.runtime-dist/src/routes/agents-task-context.js +17 -0
- package/.runtime-dist/src/routes/bundles.js +198 -0
- package/.runtime-dist/src/routes/local-host.js +49 -0
- package/.runtime-dist/src/routes/projects.js +119 -0
- package/.runtime-dist/src/routes/tasks.js +67 -0
- package/.runtime-dist/src/task/reducer.js +464 -0
- package/.runtime-dist/src/types/project.js +1 -0
- package/.runtime-dist/src/types/protocol.js +3 -0
- package/.runtime-dist/src/types/runtime.js +1 -0
- package/README.md +148 -0
- package/bin/double-penetration-host.mjs +83 -0
- package/package.json +48 -0
- package/public/index.html +1581 -0
- package/public/install-host.ps1 +64 -0
- package/scripts/run-runtime-script.mjs +43 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { COMMAND_SCHEMA_VERSION, PROTOCOL_VERSION, } from "../types/protocol.js";
|
|
2
|
+
class ReducerReject extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code) {
|
|
5
|
+
super(code);
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const ACCEPTED_BY_PHASE = {
|
|
10
|
+
new: [],
|
|
11
|
+
awareness: [],
|
|
12
|
+
collecting_understandings: ["understanding.submit", "status.upsert", "blocker.raise"],
|
|
13
|
+
alignment: ["understanding.submit", "alignment.proposed", "status.upsert", "blocker.raise"],
|
|
14
|
+
awaiting_human_confirmation: ["status.upsert", "blocker.raise"],
|
|
15
|
+
executing: ["execution.claim", "progress.update", "handoff", "task.complete", "status.upsert", "blocker.raise"],
|
|
16
|
+
blocked_or_handoff: ["execution.claim", "status.upsert", "blocker.raise"],
|
|
17
|
+
completed: ["bundle.ack"],
|
|
18
|
+
};
|
|
19
|
+
export function createTaskState(input) {
|
|
20
|
+
return {
|
|
21
|
+
snapshot: {
|
|
22
|
+
task_id: input.task_id,
|
|
23
|
+
project_id: input.project_id,
|
|
24
|
+
title: input.title,
|
|
25
|
+
phase: input.phase ?? "collecting_understandings",
|
|
26
|
+
task_version: 1,
|
|
27
|
+
event_seq: 0,
|
|
28
|
+
understanding_version: 1,
|
|
29
|
+
manifest_seq: input.manifest_seq ?? 1,
|
|
30
|
+
resolved_mode: "parallel_interpretation",
|
|
31
|
+
confirmation_required: input.confirmation_required ?? false,
|
|
32
|
+
human_confirmed: !(input.confirmation_required ?? false),
|
|
33
|
+
accepted_understanding: null,
|
|
34
|
+
facilitator_agent_id: null,
|
|
35
|
+
current_claim_token: null,
|
|
36
|
+
claimed_by_agent_id: null,
|
|
37
|
+
claim_expires_at: null,
|
|
38
|
+
attempt_count: 0,
|
|
39
|
+
blockers_open: 0,
|
|
40
|
+
updated_at: input.now,
|
|
41
|
+
},
|
|
42
|
+
events: [],
|
|
43
|
+
processed_commands: {},
|
|
44
|
+
project_status_seq: 0,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function buildAgentCommandIdempotencyKey(command) {
|
|
48
|
+
return `agent-cmd:${command.agent_id}:${command.session_id}:${command.session_epoch}:${command.command_id}`;
|
|
49
|
+
}
|
|
50
|
+
export function applyTaskCommand(state, command, ctx) {
|
|
51
|
+
const key = buildAgentCommandIdempotencyKey(command);
|
|
52
|
+
const duplicate = state.processed_commands[key];
|
|
53
|
+
if (duplicate) {
|
|
54
|
+
return { state, response: duplicate, side_effects: [] };
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
assertProtocol(command);
|
|
58
|
+
assertFence(command, ctx.current_fence);
|
|
59
|
+
assertObservedVersion(command, state.snapshot.task_version);
|
|
60
|
+
assertBundle(command, state.snapshot.manifest_seq);
|
|
61
|
+
if (command.type === "status.upsert") {
|
|
62
|
+
const payload = command.payload;
|
|
63
|
+
const next = { ...state, project_status_seq: state.project_status_seq + 1 };
|
|
64
|
+
return remember(next, key, accept(command, next.snapshot), [
|
|
65
|
+
{
|
|
66
|
+
kind: "status_upsert",
|
|
67
|
+
data: {
|
|
68
|
+
agent_id: command.agent_id,
|
|
69
|
+
task_id: command.task_id,
|
|
70
|
+
summary: payload.task_summary,
|
|
71
|
+
state: payload.state,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
if (command.type === "bundle.ack") {
|
|
77
|
+
return remember(state, key, accept(command, state.snapshot), []);
|
|
78
|
+
}
|
|
79
|
+
if (state.snapshot.confirmation_required && !state.snapshot.human_confirmed && command.type === "execution.claim") {
|
|
80
|
+
throw new ReducerReject("confirmation_required");
|
|
81
|
+
}
|
|
82
|
+
if (requiresLiveClaim(command.type)) {
|
|
83
|
+
assertLiveClaim(state.snapshot, command, ctx.now);
|
|
84
|
+
}
|
|
85
|
+
assertPhase(state.snapshot.phase, command.type);
|
|
86
|
+
if (command.type === "execution.claim" && hasLiveClaim(state.snapshot, ctx.now)) {
|
|
87
|
+
throw new ReducerReject("claim_conflict");
|
|
88
|
+
}
|
|
89
|
+
const event = commandToEvent(state.snapshot, command, ctx);
|
|
90
|
+
let nextSnapshot = rebuildSnapshot(state.snapshot, event);
|
|
91
|
+
if (command.type === "understanding.submit" &&
|
|
92
|
+
state.snapshot.phase === "collecting_understandings" &&
|
|
93
|
+
shouldAdvanceToAlignment(state.events, event)) {
|
|
94
|
+
nextSnapshot = advanceToAlignment(nextSnapshot);
|
|
95
|
+
}
|
|
96
|
+
const nextState = {
|
|
97
|
+
...state,
|
|
98
|
+
snapshot: nextSnapshot,
|
|
99
|
+
events: [...state.events, event].slice(-500),
|
|
100
|
+
};
|
|
101
|
+
const claimToken = event.event_type === "claim.granted"
|
|
102
|
+
? event.payload.claim_token
|
|
103
|
+
: undefined;
|
|
104
|
+
return remember(nextState, key, accept(command, nextSnapshot, claimToken), buildSideEffects(state.snapshot, nextSnapshot, event));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof ReducerReject) {
|
|
108
|
+
return remember(state, key, reject(command, state.snapshot, error.code), []);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function applyHumanConfirmation(state, input) {
|
|
114
|
+
const event = input.type === "accept"
|
|
115
|
+
? {
|
|
116
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
117
|
+
event_type: "human.confirmed",
|
|
118
|
+
actor_type: "human",
|
|
119
|
+
actor_id: input.actor_id,
|
|
120
|
+
payload: {},
|
|
121
|
+
occurred_at: input.now,
|
|
122
|
+
}
|
|
123
|
+
: {
|
|
124
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
125
|
+
event_type: "human.corrected",
|
|
126
|
+
actor_type: "human",
|
|
127
|
+
actor_id: input.actor_id,
|
|
128
|
+
payload: { correction_text: input.correction_text ?? "" },
|
|
129
|
+
occurred_at: input.now,
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
...state,
|
|
133
|
+
events: [...state.events, event],
|
|
134
|
+
snapshot: rebuildSnapshot(state.snapshot, event),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function applyAlarm(state, now) {
|
|
138
|
+
if (!state.snapshot.current_claim_token || !state.snapshot.claim_expires_at) {
|
|
139
|
+
return { state, expired_claim: false };
|
|
140
|
+
}
|
|
141
|
+
if (state.snapshot.claim_expires_at > now) {
|
|
142
|
+
return { state, expired_claim: false };
|
|
143
|
+
}
|
|
144
|
+
const event = {
|
|
145
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
146
|
+
event_type: "claim.expired",
|
|
147
|
+
actor_type: "system",
|
|
148
|
+
actor_id: "TaskDO",
|
|
149
|
+
payload: {
|
|
150
|
+
expired_claim_token: state.snapshot.current_claim_token,
|
|
151
|
+
expired_agent_id: state.snapshot.claimed_by_agent_id,
|
|
152
|
+
},
|
|
153
|
+
occurred_at: now,
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
state: {
|
|
157
|
+
...state,
|
|
158
|
+
events: [...state.events, event],
|
|
159
|
+
snapshot: rebuildSnapshot(state.snapshot, event),
|
|
160
|
+
},
|
|
161
|
+
expired_claim: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function scheduleAlarm(snapshot) {
|
|
165
|
+
return snapshot.claim_expires_at;
|
|
166
|
+
}
|
|
167
|
+
function assertProtocol(command) {
|
|
168
|
+
if (command.protocol_version !== PROTOCOL_VERSION) {
|
|
169
|
+
throw new ReducerReject("protocol_mismatch");
|
|
170
|
+
}
|
|
171
|
+
if (command.command_schema_version !== COMMAND_SCHEMA_VERSION) {
|
|
172
|
+
throw new ReducerReject("protocol_mismatch");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function assertPhase(phase, type) {
|
|
176
|
+
const allowed = ACCEPTED_BY_PHASE[phase] ?? [];
|
|
177
|
+
if (!allowed.includes(type)) {
|
|
178
|
+
throw new ReducerReject("phase_closed");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function assertFence(command, current) {
|
|
182
|
+
if (command.session_id !== current.session_id || command.session_epoch !== current.session_epoch) {
|
|
183
|
+
throw new ReducerReject("stale_session");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function assertObservedVersion(command, currentTaskVersion) {
|
|
187
|
+
if (command.observed_task_version < currentTaskVersion) {
|
|
188
|
+
throw new ReducerReject("task_version_conflict");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function assertBundle(command, manifestSeq) {
|
|
192
|
+
if (manifestSeq !== null && command.bundle_seq < manifestSeq) {
|
|
193
|
+
throw new ReducerReject("stale_bundle");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function requiresLiveClaim(type) {
|
|
197
|
+
return type === "progress.update" || type === "handoff" || type === "task.complete";
|
|
198
|
+
}
|
|
199
|
+
function hasLiveClaim(snapshot, now) {
|
|
200
|
+
return Boolean(snapshot.current_claim_token && snapshot.claim_expires_at && snapshot.claim_expires_at > now);
|
|
201
|
+
}
|
|
202
|
+
function assertLiveClaim(snapshot, command, now) {
|
|
203
|
+
if (!snapshot.current_claim_token) {
|
|
204
|
+
throw new ReducerReject("claim_conflict");
|
|
205
|
+
}
|
|
206
|
+
if (command.claim_token !== snapshot.current_claim_token) {
|
|
207
|
+
throw new ReducerReject("token_mismatch");
|
|
208
|
+
}
|
|
209
|
+
if (!snapshot.claim_expires_at || snapshot.claim_expires_at <= now) {
|
|
210
|
+
throw new ReducerReject("token_expired");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function commandToEvent(snapshot, command, ctx) {
|
|
214
|
+
switch (command.type) {
|
|
215
|
+
case "understanding.submit":
|
|
216
|
+
{
|
|
217
|
+
const payload = command.payload;
|
|
218
|
+
return agentEvent(command, "understanding.submitted", {
|
|
219
|
+
text: payload.text,
|
|
220
|
+
confidence: payload.confidence,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
case "alignment.proposed":
|
|
224
|
+
{
|
|
225
|
+
const payload = command.payload;
|
|
226
|
+
return agentEvent(command, "alignment.proposed", {
|
|
227
|
+
proposal_text: payload.proposal_text,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
case "execution.claim":
|
|
231
|
+
{
|
|
232
|
+
const payload = command.payload;
|
|
233
|
+
return agentEvent(command, "claim.granted", {
|
|
234
|
+
claim_token: mintClaimToken(snapshot, command, ctx),
|
|
235
|
+
granted_to_agent_id: command.agent_id,
|
|
236
|
+
expires_at: plusSeconds(ctx.now, ctx.claim_expiry_sec),
|
|
237
|
+
scope: payload.scope,
|
|
238
|
+
approach_summary: payload.approach_summary,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
case "progress.update":
|
|
242
|
+
{
|
|
243
|
+
const payload = command.payload;
|
|
244
|
+
return agentEvent(command, "progress.reported", {
|
|
245
|
+
summary: payload.summary,
|
|
246
|
+
percent_complete: payload.percent_complete,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
case "blocker.raise":
|
|
250
|
+
{
|
|
251
|
+
const payload = command.payload;
|
|
252
|
+
return agentEvent(command, "blocker.raised", {
|
|
253
|
+
message: payload.message,
|
|
254
|
+
new_approach: payload.new_approach ?? false,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
case "handoff":
|
|
258
|
+
{
|
|
259
|
+
const payload = command.payload;
|
|
260
|
+
return agentEvent(command, "handoff.accepted", {
|
|
261
|
+
to_agent_id: payload.to_agent_id ?? null,
|
|
262
|
+
reason: payload.reason,
|
|
263
|
+
context_notes: payload.context_notes,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
case "task.complete":
|
|
267
|
+
{
|
|
268
|
+
const payload = command.payload;
|
|
269
|
+
return agentEvent(command, "task.completed", {
|
|
270
|
+
summary: payload.summary,
|
|
271
|
+
artifact_ids: payload.artifact_ids ?? [],
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
throw new ReducerReject("phase_closed");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function agentEvent(command, eventType, payload) {
|
|
279
|
+
return {
|
|
280
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
281
|
+
event_type: eventType,
|
|
282
|
+
actor_type: "agent",
|
|
283
|
+
actor_id: command.agent_id,
|
|
284
|
+
payload,
|
|
285
|
+
occurred_at: command.sent_at,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function mintClaimToken(snapshot, command, ctx) {
|
|
289
|
+
return {
|
|
290
|
+
claim_token: `ctok_${crypto.randomUUID()}`,
|
|
291
|
+
task_id: snapshot.task_id,
|
|
292
|
+
task_version: snapshot.task_version,
|
|
293
|
+
granted_at: ctx.now,
|
|
294
|
+
expires_at: plusSeconds(ctx.now, ctx.claim_expiry_sec),
|
|
295
|
+
granted_to_agent_id: command.agent_id,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function rebuildSnapshot(previous, event) {
|
|
299
|
+
const next = { ...previous };
|
|
300
|
+
next.event_seq = previous.event_seq + 1;
|
|
301
|
+
next.updated_at = event.occurred_at;
|
|
302
|
+
switch (event.event_type) {
|
|
303
|
+
case "alignment.proposed":
|
|
304
|
+
next.accepted_understanding = String(event.payload.proposal_text ?? "");
|
|
305
|
+
if (next.confirmation_required) {
|
|
306
|
+
next.phase = "awaiting_human_confirmation";
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
next.phase = "executing";
|
|
310
|
+
next.human_confirmed = true;
|
|
311
|
+
next.understanding_version = previous.understanding_version + 1;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
case "human.confirmed":
|
|
315
|
+
next.phase = "executing";
|
|
316
|
+
next.human_confirmed = true;
|
|
317
|
+
next.understanding_version = previous.understanding_version + 1;
|
|
318
|
+
break;
|
|
319
|
+
case "human.corrected":
|
|
320
|
+
next.phase = "alignment";
|
|
321
|
+
next.human_confirmed = false;
|
|
322
|
+
next.accepted_understanding = String(event.payload.correction_text ?? "");
|
|
323
|
+
next.understanding_version = previous.understanding_version + 1;
|
|
324
|
+
break;
|
|
325
|
+
case "claim.granted":
|
|
326
|
+
next.phase = "executing";
|
|
327
|
+
next.current_claim_token = String(event.payload.claim_token.claim_token);
|
|
328
|
+
next.claimed_by_agent_id = String(event.payload.granted_to_agent_id);
|
|
329
|
+
next.claim_expires_at = String(event.payload.claim_token.expires_at);
|
|
330
|
+
break;
|
|
331
|
+
case "progress.reported":
|
|
332
|
+
next.phase = "executing";
|
|
333
|
+
break;
|
|
334
|
+
case "blocker.raised":
|
|
335
|
+
next.phase = "blocked_or_handoff";
|
|
336
|
+
next.attempt_count = previous.attempt_count + 1;
|
|
337
|
+
next.blockers_open = previous.blockers_open + 1;
|
|
338
|
+
next.current_claim_token = null;
|
|
339
|
+
next.claimed_by_agent_id = null;
|
|
340
|
+
next.claim_expires_at = null;
|
|
341
|
+
break;
|
|
342
|
+
case "handoff.accepted":
|
|
343
|
+
next.phase = "blocked_or_handoff";
|
|
344
|
+
next.current_claim_token = null;
|
|
345
|
+
next.claimed_by_agent_id = null;
|
|
346
|
+
next.claim_expires_at = null;
|
|
347
|
+
break;
|
|
348
|
+
case "claim.expired":
|
|
349
|
+
next.phase = "blocked_or_handoff";
|
|
350
|
+
break;
|
|
351
|
+
case "task.completed":
|
|
352
|
+
next.phase = "completed";
|
|
353
|
+
next.current_claim_token = null;
|
|
354
|
+
next.claimed_by_agent_id = null;
|
|
355
|
+
next.claim_expires_at = null;
|
|
356
|
+
break;
|
|
357
|
+
case "understanding.submitted":
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
if (canonicalSnapshotChanged(previous, next)) {
|
|
361
|
+
next.task_version = previous.task_version + 1;
|
|
362
|
+
}
|
|
363
|
+
return next;
|
|
364
|
+
}
|
|
365
|
+
function shouldAdvanceToAlignment(existingEvents, nextEvent) {
|
|
366
|
+
const agents = new Set();
|
|
367
|
+
for (const event of existingEvents) {
|
|
368
|
+
if (event.event_type === "understanding.submitted") {
|
|
369
|
+
agents.add(event.actor_id);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (nextEvent.event_type === "understanding.submitted") {
|
|
373
|
+
agents.add(nextEvent.actor_id);
|
|
374
|
+
}
|
|
375
|
+
return agents.size >= 2;
|
|
376
|
+
}
|
|
377
|
+
function advanceToAlignment(snapshot) {
|
|
378
|
+
if (snapshot.phase === "alignment") {
|
|
379
|
+
return snapshot;
|
|
380
|
+
}
|
|
381
|
+
const next = { ...snapshot };
|
|
382
|
+
next.phase = "alignment";
|
|
383
|
+
next.task_version = snapshot.task_version + 1;
|
|
384
|
+
return next;
|
|
385
|
+
}
|
|
386
|
+
function canonicalSnapshotChanged(previous, next) {
|
|
387
|
+
return (previous.phase !== next.phase ||
|
|
388
|
+
previous.understanding_version !== next.understanding_version ||
|
|
389
|
+
previous.human_confirmed !== next.human_confirmed ||
|
|
390
|
+
previous.accepted_understanding !== next.accepted_understanding ||
|
|
391
|
+
previous.current_claim_token !== next.current_claim_token ||
|
|
392
|
+
previous.claimed_by_agent_id !== next.claimed_by_agent_id ||
|
|
393
|
+
previous.claim_expires_at !== next.claim_expires_at ||
|
|
394
|
+
previous.attempt_count !== next.attempt_count ||
|
|
395
|
+
previous.blockers_open !== next.blockers_open);
|
|
396
|
+
}
|
|
397
|
+
function buildSideEffects(previous, next, event) {
|
|
398
|
+
const effects = [];
|
|
399
|
+
if (event.event_type === "alignment.proposed" || event.event_type === "human.corrected") {
|
|
400
|
+
effects.push({
|
|
401
|
+
kind: "manifest_rebuild",
|
|
402
|
+
data: {
|
|
403
|
+
reason_code: event.event_type === "alignment.proposed"
|
|
404
|
+
? "human_confirmed_understanding"
|
|
405
|
+
: "human_corrected_understanding",
|
|
406
|
+
task_id: next.task_id,
|
|
407
|
+
task_version: next.task_version,
|
|
408
|
+
expected_prev_manifest_seq: previous.manifest_seq ?? 0,
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
if (event.event_type !== "understanding.submitted") {
|
|
413
|
+
effects.push({
|
|
414
|
+
kind: "discord_mirror",
|
|
415
|
+
data: {
|
|
416
|
+
logical_key: `task:${next.task_id}:${event.event_type}:${event.actor_id}`,
|
|
417
|
+
render_version: next.task_version,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return effects;
|
|
422
|
+
}
|
|
423
|
+
function plusSeconds(now, seconds) {
|
|
424
|
+
return new Date(Date.parse(now) + seconds * 1000).toISOString();
|
|
425
|
+
}
|
|
426
|
+
function accept(command, snapshot, claimToken) {
|
|
427
|
+
return {
|
|
428
|
+
command_id: command.command_id,
|
|
429
|
+
accepted: true,
|
|
430
|
+
current_task_version: snapshot.task_version,
|
|
431
|
+
current_bundle_seq: snapshot.manifest_seq ?? undefined,
|
|
432
|
+
claim_token: claimToken,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function reject(command, snapshot, code) {
|
|
436
|
+
return {
|
|
437
|
+
command_id: command.command_id,
|
|
438
|
+
accepted: false,
|
|
439
|
+
current_task_version: snapshot.task_version,
|
|
440
|
+
current_bundle_seq: snapshot.manifest_seq ?? undefined,
|
|
441
|
+
rejection: {
|
|
442
|
+
code,
|
|
443
|
+
message: code,
|
|
444
|
+
current_task_version: snapshot.task_version,
|
|
445
|
+
current_bundle_seq: snapshot.manifest_seq ?? undefined,
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const MAX_PROCESSED_COMMANDS = 500;
|
|
450
|
+
function remember(state, key, response, sideEffects) {
|
|
451
|
+
let processed = { ...state.processed_commands, [key]: response };
|
|
452
|
+
const keys = Object.keys(processed);
|
|
453
|
+
if (keys.length > MAX_PROCESSED_COMMANDS) {
|
|
454
|
+
const excess = keys.length - MAX_PROCESSED_COMMANDS;
|
|
455
|
+
for (let i = 0; i < excess; i++) {
|
|
456
|
+
delete processed[keys[i]];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
state: { ...state, processed_commands: processed },
|
|
461
|
+
response,
|
|
462
|
+
side_effects: sideEffects,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Office Core
|
|
2
|
+
|
|
3
|
+
Office Core is a Cloudflare-hosted control plane with an installable local CLI for Codex and Claude agents.
|
|
4
|
+
|
|
5
|
+
The product model is simple:
|
|
6
|
+
|
|
7
|
+
- one browser UI
|
|
8
|
+
- one active room per project
|
|
9
|
+
- one or more connected machines
|
|
10
|
+
- fresh Codex or Claude agents spawned into the current room
|
|
11
|
+
- room context compacted into `summary.txt`
|
|
12
|
+
- agent startup grounded only on `onboarding.txt` and the current room summary
|
|
13
|
+
|
|
14
|
+
This repo contains:
|
|
15
|
+
|
|
16
|
+
- the Worker and browser UI
|
|
17
|
+
- the local host runtime
|
|
18
|
+
- the local install and host menu scripts
|
|
19
|
+
- the room, host, and task state model
|
|
20
|
+
|
|
21
|
+
## Project shape
|
|
22
|
+
|
|
23
|
+
- `public/`
|
|
24
|
+
- chat-first browser UI
|
|
25
|
+
- install script served by the Worker
|
|
26
|
+
- `src/`
|
|
27
|
+
- Worker routes
|
|
28
|
+
- Durable Objects
|
|
29
|
+
- project read-models
|
|
30
|
+
- `scripts/`
|
|
31
|
+
- local host runtime
|
|
32
|
+
- install flow
|
|
33
|
+
- host menu and helper scripts
|
|
34
|
+
- `bin/`
|
|
35
|
+
- CLI entrypoint
|
|
36
|
+
- `test/`
|
|
37
|
+
- node test suite
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- Node.js 20+
|
|
42
|
+
- npm
|
|
43
|
+
- Cloudflare Wrangler for local Worker development
|
|
44
|
+
- Windows if you want the visible local shell flow exactly as designed
|
|
45
|
+
|
|
46
|
+
Optional local runners:
|
|
47
|
+
|
|
48
|
+
- `codex`
|
|
49
|
+
- `claude`
|
|
50
|
+
|
|
51
|
+
## Install the CLI
|
|
52
|
+
|
|
53
|
+
Install from a packaged tarball served by your Worker:
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
npm install -g "https://your-worker.example/downloads/office-core.tgz"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then launch the interactive CLI:
|
|
60
|
+
|
|
61
|
+
```powershell
|
|
62
|
+
office-core
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Primary command:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
office-core
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Supported aliases:
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
office-host
|
|
75
|
+
dp-host
|
|
76
|
+
double-penetration-host
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If you later publish to npm, the install becomes:
|
|
80
|
+
|
|
81
|
+
```powershell
|
|
82
|
+
npm install -g office-core
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
or:
|
|
86
|
+
|
|
87
|
+
```powershell
|
|
88
|
+
npx office-core
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Install for local development
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
cd "C:\Users\Michael Goat Penis\Dev\Office-Core"
|
|
95
|
+
npm install
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Run locally
|
|
99
|
+
|
|
100
|
+
Window 1:
|
|
101
|
+
|
|
102
|
+
```powershell
|
|
103
|
+
cd "C:\Users\Michael Goat Penis\Dev\Office-Core"
|
|
104
|
+
npm run dev
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Window 2:
|
|
108
|
+
|
|
109
|
+
```powershell
|
|
110
|
+
cd "C:\Users\Michael Goat Penis\Dev\Office-Core"
|
|
111
|
+
node .\bin\double-penetration-host.mjs install --baseUrl "http://127.0.0.1:8787" --project "prj_local" --workdir "C:\Users\Michael Goat Penis\Dev\Office-Core"
|
|
112
|
+
node .\bin\double-penetration-host.mjs start
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Open:
|
|
116
|
+
|
|
117
|
+
- [http://127.0.0.1:8787/?projectId=prj_local](http://127.0.0.1:8787/?projectId=prj_local)
|
|
118
|
+
|
|
119
|
+
## Useful commands
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
npm run check
|
|
123
|
+
npm run check:runtime
|
|
124
|
+
npm test
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```powershell
|
|
128
|
+
node .\bin\double-penetration-host.mjs help
|
|
129
|
+
node .\bin\double-penetration-host.mjs doctor
|
|
130
|
+
node .\bin\double-penetration-host.mjs start
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Current product behavior
|
|
134
|
+
|
|
135
|
+
- conversations are room-first
|
|
136
|
+
- old conversations keep compacted context, not sleeping agents
|
|
137
|
+
- `Add Agent` spawns a fresh visible local shell for the current room
|
|
138
|
+
- room messages are delivered to session inbox/outbox paths
|
|
139
|
+
- agent/system replies are posted back into the room
|
|
140
|
+
- agent recursion is guarded so agents do not endlessly answer each other
|
|
141
|
+
|
|
142
|
+
## Packaging
|
|
143
|
+
|
|
144
|
+
- `npm pack` produces a prebuilt installable package
|
|
145
|
+
- the tarball is copied to `public/downloads/office-core.tgz`
|
|
146
|
+
- published installs can use `npm install -g office-core`
|
|
147
|
+
- the package includes the compiled `.runtime-dist` runtime, so installed machines do not need TypeScript just to run the CLI
|
|
148
|
+
- repo development can still use `node .\bin\double-penetration-host.mjs` without publishing or linking
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const runtimeRunner = path.join(rootDir, "scripts", "run-runtime-script.mjs");
|
|
11
|
+
|
|
12
|
+
const argv = process.argv.slice(2);
|
|
13
|
+
const [command, ...rest] = argv;
|
|
14
|
+
|
|
15
|
+
if (!command) {
|
|
16
|
+
runScript("scripts/office-cli.ts", rest);
|
|
17
|
+
} else if (command === "menu") {
|
|
18
|
+
runScript("scripts/host-menu.ts", rest);
|
|
19
|
+
} else if (command === "install" || command === "join") {
|
|
20
|
+
runScript("scripts/install-host.ts", rest);
|
|
21
|
+
} else if (command === "start") {
|
|
22
|
+
runScript("scripts/home-agent-host.ts", rest);
|
|
23
|
+
} else if (command === "doctor") {
|
|
24
|
+
runScript("scripts/host-doctor.ts", rest);
|
|
25
|
+
} else if (command === "open") {
|
|
26
|
+
runScript("scripts/host-open.ts", rest);
|
|
27
|
+
} else if (command === "login") {
|
|
28
|
+
runScript("scripts/host-login.ts", rest);
|
|
29
|
+
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
30
|
+
printHelp();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
} else if (command === "version" || command === "--version" || command === "-v") {
|
|
33
|
+
const pkg = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
|
34
|
+
console.log(pkg.version);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
} else {
|
|
37
|
+
console.error(`Unknown command: ${command}`);
|
|
38
|
+
printHelp();
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function runScript(relativeScript, args) {
|
|
43
|
+
const result = spawnSync(process.execPath, [runtimeRunner, relativeScript, ...args], {
|
|
44
|
+
cwd: rootDir,
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
env: process.env,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (typeof result.status === "number") {
|
|
50
|
+
process.exit(result.status);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printHelp() {
|
|
57
|
+
console.log(
|
|
58
|
+
[
|
|
59
|
+
"office-core",
|
|
60
|
+
"Aliases: office-host, dp-host, double-penetration-host",
|
|
61
|
+
"",
|
|
62
|
+
"Usage:",
|
|
63
|
+
" office-core Interactive CLI (default)",
|
|
64
|
+
" office-core menu Legacy numbered menu",
|
|
65
|
+
" office-core install Register this machine and save host config",
|
|
66
|
+
" office-core start Start the configured local host daemon",
|
|
67
|
+
" office-core doctor Check worker health, config, and local runners",
|
|
68
|
+
" office-core open Open the configured Office Core UI",
|
|
69
|
+
" office-core login codex Run Codex login locally",
|
|
70
|
+
" office-core login claude Run Claude auth locally",
|
|
71
|
+
"",
|
|
72
|
+
"Legacy aliases continue to work:",
|
|
73
|
+
" office-host install --baseUrl https://your-worker.example --project prj_local --workdir C:\\repo",
|
|
74
|
+
" dp-host install --baseUrl https://your-worker.example --project prj_local --workdir C:\\repo",
|
|
75
|
+
" double-penetration-host start",
|
|
76
|
+
"",
|
|
77
|
+
"Examples:",
|
|
78
|
+
" office-core install --baseUrl https://your-worker.example --project prj_local --workdir C:\\repo",
|
|
79
|
+
" office-core start",
|
|
80
|
+
" office-core doctor",
|
|
81
|
+
].join("\n"),
|
|
82
|
+
);
|
|
83
|
+
}
|