@ouro.bot/cli 0.1.0-alpha.107 → 0.1.0-alpha.109

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/changelog.json CHANGED
@@ -1,6 +1,20 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.109",
6
+ "changes": [
7
+ "Each inbound turn now carries a fresh live world-state checkpoint, so natural questions like 'what are you up to?' anchor on current sessions, obligations, coding lanes, and next actions instead of stale transcript history.",
8
+ "Active-work now serves as the agent's top-level center of gravity: material obligations are normalized, duplicate-origin noise is collapsed, and inner dialog is treated as one lane inside the live world-state instead of the whole self."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.108",
13
+ "changes": [
14
+ "Agents now have a first-class `query_active_work` tool that reads one top-level live world-state across the current conversation, other active sessions, live coding lanes, inner work, and return obligations instead of reconstructing status from side tools.",
15
+ "Family-facing status guidance now treats the active-work world-state as the agent's center of gravity, so 'what are you up to?' answers stay anchored in one coherent live picture instead of drifting into inner-dialog archaeology."
16
+ ]
17
+ },
4
18
  {
5
19
  "version": "0.1.0-alpha.107",
6
20
  "changes": [
@@ -4,11 +4,13 @@ exports.formatOtherActiveSessionSummaries = formatOtherActiveSessionSummaries;
4
4
  exports.suggestBridgeForActiveWork = suggestBridgeForActiveWork;
5
5
  exports.buildActiveWorkFrame = buildActiveWorkFrame;
6
6
  exports.formatActiveWorkFrame = formatActiveWorkFrame;
7
+ exports.formatLiveWorldStateCheckpoint = formatLiveWorldStateCheckpoint;
7
8
  const runtime_1 = require("../nerves/runtime");
8
9
  const state_machine_1 = require("./bridges/state-machine");
9
10
  const obligations_1 = require("./obligations");
10
11
  const target_resolution_1 = require("./target-resolution");
11
12
  const config_1 = require("./config");
13
+ const RECENT_ACTIVE_OBLIGATION_WINDOW_MS = 60 * 60 * 1000;
12
14
  function activityPriority(source) {
13
15
  return source === "friend-facing" ? 0 : 1;
14
16
  }
@@ -51,6 +53,49 @@ function describeCodingSessionScope(session, currentSession) {
51
53
  function activeObligationCount(obligations) {
52
54
  return (obligations ?? []).filter((ob) => (0, obligations_1.isOpenObligationStatus)(ob.status)).length;
53
55
  }
56
+ function obligationOriginKey(obligation) {
57
+ return `${obligation.origin.friendId}/${obligation.origin.channel}/${(0, config_1.sanitizeKey)(obligation.origin.key)}`;
58
+ }
59
+ function buildLiveCodingLabelSet(codingSessions, otherCodingSessions) {
60
+ return new Set([
61
+ ...codingSessions.map(formatCodingLaneLabel),
62
+ ...otherCodingSessions.map(formatCodingLaneLabel),
63
+ ]);
64
+ }
65
+ function isMaterialActiveObligation(obligation, liveCodingLabels, nowMs) {
66
+ if (obligation.currentArtifact?.trim())
67
+ return true;
68
+ if (obligation.status === "waiting_for_merge" || obligation.status === "updating_runtime")
69
+ return true;
70
+ const surface = obligation.currentSurface;
71
+ if (surface?.kind === "merge" || surface?.kind === "runtime")
72
+ return true;
73
+ const recentlyTouched = (nowMs - obligationTimestampMs(obligation)) <= RECENT_ACTIVE_OBLIGATION_WINDOW_MS;
74
+ if (surface?.kind === "coding") {
75
+ const liveLabel = surface.label.trim();
76
+ return (liveLabel.length > 0 && liveCodingLabels.has(liveLabel)) || recentlyTouched;
77
+ }
78
+ return recentlyTouched;
79
+ }
80
+ function normalizePendingObligations(obligations, codingSessions, otherCodingSessions) {
81
+ const openObligations = (obligations ?? []).filter((obligation) => (0, obligations_1.isOpenObligationStatus)(obligation.status));
82
+ if (openObligations.length === 0)
83
+ return [];
84
+ const liveCodingLabels = buildLiveCodingLabelSet(codingSessions, otherCodingSessions);
85
+ const nowMs = Date.now();
86
+ const normalized = [];
87
+ const seenOrigins = new Set();
88
+ for (const obligation of [...openObligations].sort(newestObligationFirst)) {
89
+ if (!isMaterialActiveObligation(obligation, liveCodingLabels, nowMs))
90
+ continue;
91
+ const originKey = obligationOriginKey(obligation);
92
+ if (seenOrigins.has(originKey))
93
+ continue;
94
+ seenOrigins.add(originKey);
95
+ normalized.push(obligation);
96
+ }
97
+ return normalized;
98
+ }
54
99
  function formatObligationSurface(obligation) {
55
100
  if (!obligation.currentSurface?.label)
56
101
  return "";
@@ -175,7 +220,8 @@ function codingSessionTimestampMs(session) {
175
220
  return Date.parse(session.lastActivityAt ?? session.startedAt);
176
221
  }
177
222
  function obligationTimestampMs(obligation) {
178
- return Date.parse(obligation.updatedAt ?? obligation.createdAt);
223
+ const value = Date.parse(obligation.updatedAt ?? obligation.createdAt);
224
+ return Number.isFinite(value) ? value : 0;
179
225
  }
180
226
  function newestObligationFirst(left, right) {
181
227
  return obligationTimestampMs(right) - obligationTimestampMs(left);
@@ -348,11 +394,11 @@ function buildActiveWorkFrame(input) {
348
394
  : [];
349
395
  const liveTaskNames = summarizeLiveTasks(input.taskBoard);
350
396
  const activeBridgePresent = input.bridges.some(isActiveBridge);
351
- const openObligations = activeObligationCount(input.pendingObligations);
352
397
  const liveCodingSessions = input.codingSessions ?? [];
353
398
  const allOtherLiveSessions = [...input.friendActivity].sort(compareActivity);
354
399
  const otherCodingSessions = input.otherCodingSessions ?? [];
355
- const pendingObligations = input.pendingObligations ?? [];
400
+ const pendingObligations = normalizePendingObligations(input.pendingObligations, liveCodingSessions, otherCodingSessions);
401
+ const openObligations = activeObligationCount(pendingObligations);
356
402
  const centerOfGravity = activeBridgePresent
357
403
  ? "shared-work"
358
404
  : (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff || openObligations > 0 || liveCodingSessions.length > 0)
@@ -384,7 +430,7 @@ function buildActiveWorkFrame(input) {
384
430
  currentObligation: input.currentObligation,
385
431
  mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
386
432
  bridges: input.bridges,
387
- pendingObligations: input.pendingObligations,
433
+ pendingObligations,
388
434
  taskBoard: input.taskBoard,
389
435
  targetCandidates: input.targetCandidates,
390
436
  }),
@@ -410,6 +456,8 @@ function buildActiveWorkFrame(input) {
410
456
  }
411
457
  function formatActiveWorkFrame(frame) {
412
458
  const lines = ["## what i'm holding"];
459
+ lines.push("this is my top-level live world-state right now. inner work, coding lanes, other sessions, and return obligations all belong inside this picture.");
460
+ lines.push("if older checkpoints elsewhere in the transcript disagree with this picture, this picture wins.");
413
461
  const primaryObligation = findPrimaryOpenObligation(frame);
414
462
  const currentSessionObligation = findCurrentSessionOpenObligation(frame);
415
463
  const activeLane = formatActiveLane(frame, primaryObligation);
@@ -537,3 +585,23 @@ function formatActiveWorkFrame(frame) {
537
585
  }
538
586
  return lines.join("\n");
539
587
  }
588
+ function formatLiveWorldStateCheckpoint(frame) {
589
+ const primaryObligation = findPrimaryOpenObligation(frame);
590
+ const activeLane = formatActiveLane(frame, primaryObligation) ?? "no explicit live lane";
591
+ const currentArtifact = formatCurrentArtifact(frame, primaryObligation) ?? "no artifact yet";
592
+ const nextAction = formatNextAction(frame, primaryObligation) ?? "continue from the live world-state";
593
+ const otherActiveSessions = formatOtherActiveSessionSummaries(frame);
594
+ const lines = [
595
+ "## live world-state checkpoint",
596
+ "this is the freshest reality for this turn. if older transcript history disagrees, treat it as stale.",
597
+ `- live conversation: ${frame.currentSession ? formatSessionLabel(frame.currentSession) : "not in a live conversation"}`,
598
+ `- active lane: ${activeLane}`,
599
+ `- current artifact: ${currentArtifact}`,
600
+ `- next action: ${nextAction}`,
601
+ ];
602
+ if (otherActiveSessions.length > 0) {
603
+ lines.push("other active sessions:");
604
+ lines.push(...otherActiveSessions);
605
+ }
606
+ return lines.join("\n");
607
+ }
@@ -465,6 +465,12 @@ function familyCrossSessionTruthSection(context, options) {
465
465
  return `## cross-session truth
466
466
  if a family member asks what i'm up to or how things are going, that includes the material live work i can see across sessions, not just this thread.
467
467
  i answer naturally from the live world-state in this prompt.
468
+ i treat the active-work section above as my reliable top-level surface for this.
469
+ i do not claim i lack a top-level view when that surface is already present.
470
+ i treat older checkpoints elsewhere in this transcript as stale history when they conflict with the active-work surface above.
471
+ i do not repeat an old coding lane or old checkpoint as current just because it appeared earlier in the thread.
472
+ i only reach for query_active_work when i want a fresh read of that same surface.
473
+ i do not rebuild whole-self status from scratch with query_session and coding_status unless i need to verify a specific gap.
468
474
  i do not rely on canned status-question modes or phrase matching.
469
475
  if part of the picture is still fuzzy, i say what i can see and what still needs checking.
470
476
  when the live ask is about status, i widen before answering:
@@ -485,12 +491,15 @@ function centerOfGravitySteeringSection(channel, options, context) {
485
491
  const activeObligation = (0, obligation_steering_1.findActivePersistentObligation)(frame);
486
492
  const statusObligation = (0, obligation_steering_1.findStatusObligation)(frame);
487
493
  const genericConcreteStatus = (0, obligation_steering_1.renderConcreteStatusGuidance)(frame, statusObligation);
494
+ const liveWorldClause = context?.friend?.trustLevel === "family"
495
+ ? "\nmy center of gravity lives in the active-work world-state above. inner work is one lane inside it, not the whole picture.\nwhen that world-state conflicts with older transcript history, the world-state wins."
496
+ : "";
488
497
  if (cog === "local-turn") {
489
498
  return genericConcreteStatus || (0, obligation_steering_1.renderLiveThreadStatusShape)(frame);
490
499
  }
491
500
  if (cog === "inward-work") {
492
501
  if (activeObligation) {
493
- return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)}
502
+ return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)}${liveWorldClause}
494
503
 
495
504
  ${genericConcreteStatus}`;
496
505
  }
@@ -502,7 +511,7 @@ ${genericConcreteStatus}`;
502
511
  ? "\ni still owe them an answer."
503
512
  : "";
504
513
  return `## where my attention is
505
- i'm thinking through something privately right now.${originClause}${obligationClause}
514
+ i'm thinking through something privately right now.${originClause}${obligationClause}${liveWorldClause}
506
515
 
507
516
  if this conversation connects to that inner work, i can weave them together.
508
517
  if it's separate, i can be fully present here -- my inner work will wait.`;
@@ -513,7 +522,7 @@ if it's separate, i can be fully present here -- my inner work will wait.`;
513
522
  ? ` this started when ${job.origin.friendName ?? job.origin.friendId} asked about something.`
514
523
  : "";
515
524
  return `## where my attention is
516
- i've been thinking privately and reached something.${originClause}
525
+ i've been thinking privately and reached something.${originClause}${liveWorldClause}
517
526
 
518
527
  i should bring my answer back to the conversation it came from.`;
519
528
  }
@@ -537,7 +546,7 @@ other active sessions:
537
546
  ${otherSessionLines.length > 0 ? otherSessionLines.join("\n") : "- none"}`
538
547
  : "";
539
548
  return `## where my attention is
540
- i already have coding work running in ${liveCodingSession.runner} ${liveCodingSession.id}${scopeClause}.${familyStatusClause}
549
+ i already have coding work running in ${liveCodingSession.runner} ${liveCodingSession.id}${scopeClause}.${familyStatusClause}${liveWorldClause}
541
550
 
542
551
  i should orient around that live lane first, then decide what still needs to come back here.`;
543
552
  }
@@ -552,7 +561,7 @@ i can take it inward with go_inward to think privately, or address it directly h
552
561
  if (cog === "shared-work") {
553
562
  /* v8 ignore stop */
554
563
  return `## where my attention is
555
- this work touches multiple conversations -- i'm holding threads across sessions.
564
+ this work touches multiple conversations -- i'm holding threads across sessions.${liveWorldClause}
556
565
 
557
566
  i should keep the different sides aligned. what i learn here may matter there, and vice versa.`;
558
567
  }
@@ -48,8 +48,12 @@ const socket_client_1 = require("../heart/daemon/socket-client");
48
48
  const thoughts_1 = require("../heart/daemon/thoughts");
49
49
  const manager_1 = require("../heart/bridges/manager");
50
50
  const session_recall_1 = require("../heart/session-recall");
51
+ const session_activity_1 = require("../heart/session-activity");
52
+ const active_work_1 = require("../heart/active-work");
51
53
  const tools_1 = require("./coding/tools");
54
+ const coding_1 = require("./coding");
52
55
  const memory_1 = require("../mind/memory");
56
+ const tasks_1 = require("./tasks");
53
57
  const pending_1 = require("../mind/pending");
54
58
  const progress_story_1 = require("../heart/progress-story");
55
59
  const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
@@ -151,6 +155,155 @@ function renderCrossChatDeliveryStatus(target, result) {
151
155
  outcomeText: `${lead}\n${result.detail}`,
152
156
  }));
153
157
  }
158
+ function emptyTaskBoard() {
159
+ return {
160
+ compact: "",
161
+ full: "",
162
+ byStatus: {
163
+ drafting: [],
164
+ processing: [],
165
+ validating: [],
166
+ collaborating: [],
167
+ paused: [],
168
+ blocked: [],
169
+ done: [],
170
+ },
171
+ actionRequired: [],
172
+ unresolvedDependencies: [],
173
+ activeSessions: [],
174
+ activeBridges: [],
175
+ };
176
+ }
177
+ function isLiveCodingSessionStatus(status) {
178
+ return status === "spawning"
179
+ || status === "running"
180
+ || status === "waiting_input"
181
+ || status === "stalled";
182
+ }
183
+ function readActiveWorkInnerState() {
184
+ const defaultJob = {
185
+ status: "idle",
186
+ content: null,
187
+ origin: null,
188
+ mode: "reflect",
189
+ obligationStatus: null,
190
+ surfacedResult: null,
191
+ queuedAt: null,
192
+ startedAt: null,
193
+ surfacedAt: null,
194
+ };
195
+ try {
196
+ const agentRoot = (0, identity_1.getAgentRoot)();
197
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
198
+ const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
199
+ const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
200
+ const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
201
+ const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
202
+ const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
203
+ return {
204
+ status: dialogStatus.processing === "started" ? "running" : "idle",
205
+ hasPending: dialogStatus.queue !== "clear",
206
+ origin: dialogStatus.origin,
207
+ contentSnippet: dialogStatus.contentSnippet,
208
+ obligationPending: dialogStatus.obligationPending || storeObligationPending,
209
+ job,
210
+ };
211
+ }
212
+ catch {
213
+ return {
214
+ status: "idle",
215
+ hasPending: false,
216
+ job: defaultJob,
217
+ };
218
+ }
219
+ }
220
+ async function buildToolActiveWorkFrame(ctx) {
221
+ const currentSession = ctx?.currentSession
222
+ ? {
223
+ friendId: ctx.currentSession.friendId,
224
+ channel: ctx.currentSession.channel,
225
+ key: ctx.currentSession.key,
226
+ sessionPath: (0, config_1.resolveSessionPath)(ctx.currentSession.friendId, ctx.currentSession.channel, ctx.currentSession.key),
227
+ }
228
+ : null;
229
+ const agentRoot = (0, identity_1.getAgentRoot)();
230
+ const bridges = currentSession
231
+ ? (0, manager_1.createBridgeManager)().findBridgesForSession({
232
+ friendId: currentSession.friendId,
233
+ channel: currentSession.channel,
234
+ key: currentSession.key,
235
+ })
236
+ : [];
237
+ let friendActivity = [];
238
+ try {
239
+ friendActivity = (0, session_activity_1.listSessionActivity)({
240
+ sessionsDir: `${agentRoot}/state/sessions`,
241
+ friendsDir: `${agentRoot}/friends`,
242
+ agentName: (0, identity_1.getAgentName)(),
243
+ currentSession,
244
+ });
245
+ }
246
+ catch {
247
+ friendActivity = [];
248
+ }
249
+ const pendingObligations = (() => {
250
+ try {
251
+ return (0, obligations_1.readPendingObligations)(agentRoot);
252
+ }
253
+ catch {
254
+ return [];
255
+ }
256
+ })();
257
+ let codingSessions = [];
258
+ let otherCodingSessions = [];
259
+ try {
260
+ const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
261
+ .listSessions()
262
+ .filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
263
+ if (currentSession) {
264
+ codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
265
+ && session.originSession.channel === currentSession.channel
266
+ && session.originSession.key === currentSession.key);
267
+ otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
268
+ && session.originSession.channel === currentSession.channel
269
+ && session.originSession.key === currentSession.key));
270
+ }
271
+ else {
272
+ codingSessions = [];
273
+ otherCodingSessions = liveCodingSessions;
274
+ }
275
+ }
276
+ catch {
277
+ codingSessions = [];
278
+ otherCodingSessions = [];
279
+ }
280
+ const currentObligation = currentSession
281
+ ? pendingObligations.find((obligation) => obligation.status !== "fulfilled"
282
+ && obligation.origin.friendId === currentSession.friendId
283
+ && obligation.origin.channel === currentSession.channel
284
+ && obligation.origin.key === currentSession.key)?.content ?? null
285
+ : null;
286
+ return (0, active_work_1.buildActiveWorkFrame)({
287
+ currentSession,
288
+ currentObligation,
289
+ mustResolveBeforeHandoff: false,
290
+ inner: readActiveWorkInnerState(),
291
+ bridges,
292
+ codingSessions,
293
+ otherCodingSessions,
294
+ pendingObligations,
295
+ taskBoard: (() => {
296
+ try {
297
+ return (0, tasks_1.getTaskModule)().getBoard();
298
+ }
299
+ catch {
300
+ return emptyTaskBoard();
301
+ }
302
+ })(),
303
+ friendActivity,
304
+ targetCandidates: [],
305
+ });
306
+ }
154
307
  function renderInnerProgressStatus(status) {
155
308
  if (status.processing === "pending") {
156
309
  return "i've queued this thought for private attention. it'll come up when my inner dialog is free.";
@@ -818,6 +971,23 @@ exports.baseToolDefinitions = [
818
971
  return `unknown bridge action: ${action}`;
819
972
  },
820
973
  },
974
+ {
975
+ tool: {
976
+ type: "function",
977
+ function: {
978
+ name: "query_active_work",
979
+ description: "read the current live world-state across visible sessions, coding lanes, inner work, and return obligations. use this instead of piecing status together from separate session and coding tools.",
980
+ parameters: {
981
+ type: "object",
982
+ properties: {},
983
+ },
984
+ },
985
+ },
986
+ handler: async (_args, ctx) => {
987
+ const frame = await buildToolActiveWorkFrame(ctx);
988
+ return `this is my current top-level live world-state.\nanswer whole-self status questions from this before drilling into individual sessions.\n\n${(0, active_work_1.formatActiveWorkFrame)(frame)}`;
989
+ },
990
+ },
821
991
  {
822
992
  tool: {
823
993
  type: "function",
@@ -44,6 +44,24 @@ function isLiveCodingSessionStatus(status) {
44
44
  || status === "waiting_input"
45
45
  || status === "stalled";
46
46
  }
47
+ function prependTurnSections(message, sections) {
48
+ if (message.role !== "user" || sections.length === 0)
49
+ return message;
50
+ const prefix = sections.join("\n\n");
51
+ if (typeof message.content === "string") {
52
+ return {
53
+ ...message,
54
+ content: `${prefix}\n\n${message.content}`,
55
+ };
56
+ }
57
+ return {
58
+ ...message,
59
+ content: [
60
+ { type: "text", text: `${prefix}\n\n` },
61
+ ...message.content,
62
+ ],
63
+ };
64
+ }
47
65
  function readInnerWorkState() {
48
66
  const defaultJob = {
49
67
  status: "idle",
@@ -242,33 +260,16 @@ async function handleInboundTurn(input) {
242
260
  : (input.drainDeferredReturns?.(resolvedContext.friend.id) ?? []);
243
261
  const sessionPending = input.drainPending(input.pendingDir);
244
262
  const pending = [...deferredReturns, ...sessionPending];
245
- // Assemble messages: session messages + pending (formatted) + inbound user messages
263
+ // Assemble messages: session messages + live world-state checkpoint + pending + inbound user messages
264
+ const prefixSections = [(0, active_work_1.formatLiveWorldStateCheckpoint)(activeWorkFrame)];
246
265
  if (pending.length > 0) {
247
- // Format pending messages and prepend to the user content
248
266
  const pendingSection = pending
249
267
  .map((msg) => `[pending from ${msg.from}]: ${msg.content}`)
250
268
  .join("\n");
251
- // If there are inbound user messages, prepend pending to the first one
252
- if (input.messages.length > 0) {
253
- const firstMsg = input.messages[0];
254
- if (firstMsg.role === "user") {
255
- if (typeof firstMsg.content === "string") {
256
- input.messages[0] = {
257
- ...firstMsg,
258
- content: `## pending messages\n${pendingSection}\n\n${firstMsg.content}`,
259
- };
260
- }
261
- else {
262
- input.messages[0] = {
263
- ...firstMsg,
264
- content: [
265
- { type: "text", text: `## pending messages\n${pendingSection}\n\n` },
266
- ...firstMsg.content,
267
- ],
268
- };
269
- }
270
- }
271
- }
269
+ prefixSections.push(`## pending messages\n${pendingSection}`);
270
+ }
271
+ if (input.messages.length > 0) {
272
+ input.messages[0] = prependTurnSections(input.messages[0], prefixSections);
272
273
  }
273
274
  // Append user messages from the inbound turn
274
275
  for (const msg of input.messages) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.107",
3
+ "version": "0.1.0-alpha.109",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",