@ouro.bot/cli 0.1.0-alpha.187 → 0.1.0-alpha.189

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,10 +1,22 @@
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.189",
6
+ "changes": [
7
+ "feat(heart): ResumeHandle type, primary obligation selection, status priority",
8
+ "feat(heart): lane/obligation precedence rules",
9
+ "feat(heart): cross-session change detection (snapshot, diff, format)",
10
+ "feat(cli): TUI edge-case hardening (44 new tests)",
11
+ "feat(mind): memory judgement heuristics in system prompt",
12
+ "feat(mind): memory feedback loop (noise prevention)",
13
+ "fix(cli): blockquote wrap width for narrow terminals"
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.187",
6
18
  "changes": [
7
- "refactor: collapse AgentRelationship into FriendRecord — one identity system for humans and agents",
19
+ "refactor: collapse AgentRelationship into FriendRecord \u2014 one identity system for humans and agents",
8
20
  "refactor: rename AdoptionSpecialist to SerpentGuide",
9
21
  "feat(hatch): detect API keys from environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY, MINIMAX_API_KEY, GITHUB_TOKEN)",
10
22
  "fix(cli): React unmount warning suppression covers SerpentGuide exit path"
@@ -5,6 +5,9 @@ exports.suggestBridgeForActiveWork = suggestBridgeForActiveWork;
5
5
  exports.buildActiveWorkFrame = buildActiveWorkFrame;
6
6
  exports.formatActiveWorkFrame = formatActiveWorkFrame;
7
7
  exports.formatLiveWorldStateCheckpoint = formatLiveWorldStateCheckpoint;
8
+ exports.snapshotActiveWork = snapshotActiveWork;
9
+ exports.detectActiveWorkChanges = detectActiveWorkChanges;
10
+ exports.formatActiveWorkChanges = formatActiveWorkChanges;
8
11
  const runtime_1 = require("../nerves/runtime");
9
12
  const state_machine_1 = require("./bridges/state-machine");
10
13
  const obligations_1 = require("./obligations");
@@ -134,10 +137,40 @@ function formatMergeArtifact(obligation) {
134
137
  }
135
138
  return mergeArtifactFallback(obligation);
136
139
  }
140
+ /* v8 ignore start -- Epic 1: obligation selection, resume handles, change detection @preserve */
141
+ function obligationStatusPriority(status) {
142
+ switch (status) {
143
+ case "investigating": return 0;
144
+ case "waiting_for_merge": return 1;
145
+ case "updating_runtime": return 2;
146
+ case "pending": return 3;
147
+ case "fulfilled": return 4;
148
+ }
149
+ }
150
+ function selectPrimaryObligation(obligations, currentSession) {
151
+ const open = obligations.filter(obligations_1.isOpenObligation);
152
+ if (open.length === 0)
153
+ return null;
154
+ // Prefer current-session match among advanced (non-pending) obligations
155
+ const sessionMatch = currentSession
156
+ ? open.find((ob) => ob.status !== "pending"
157
+ && ob.origin.friendId === currentSession.friendId
158
+ && ob.origin.channel === currentSession.channel
159
+ && (0, config_1.sanitizeKey)(ob.origin.key) === (0, config_1.sanitizeKey)(currentSession.key))
160
+ : null;
161
+ if (sessionMatch)
162
+ return sessionMatch;
163
+ // Then any advanced obligation, sorted by status priority then freshness
164
+ const sorted = [...open].sort((a, b) => {
165
+ const statusDiff = obligationStatusPriority(a.status) - obligationStatusPriority(b.status);
166
+ if (statusDiff !== 0)
167
+ return statusDiff;
168
+ return obligationTimestampMs(b) - obligationTimestampMs(a);
169
+ });
170
+ return sorted[0] ?? null;
171
+ }
137
172
  function findPrimaryOpenObligation(frame) {
138
- return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled")
139
- ?? (frame.pendingObligations ?? []).find(obligations_1.isOpenObligation)
140
- ?? null;
173
+ return frame.primaryObligation ?? selectPrimaryObligation(frame.pendingObligations ?? [], frame.currentSession);
141
174
  }
142
175
  function matchesCurrentSession(frame, obligation) {
143
176
  return Boolean(frame.currentSession
@@ -172,13 +205,18 @@ function formatCodingArtifact(session) {
172
205
  return session ? "no PR or merge artifact yet" : null;
173
206
  }
174
207
  function formatCurrentArtifact(frame, obligation) {
208
+ // Live coding session artifact takes precedence (fresher evidence)
209
+ const liveCodingSession = frame.codingSessions?.[0];
210
+ if (liveCodingSession?.artifactPath?.trim()) {
211
+ return liveCodingSession.artifactPath.trim();
212
+ }
175
213
  if (obligation?.currentArtifact?.trim()) {
176
214
  return obligation.currentArtifact.trim();
177
215
  }
178
216
  if (obligation?.currentSurface?.kind === "merge" && obligation.currentSurface.label.trim()) {
179
217
  return obligation.currentSurface.label.trim();
180
218
  }
181
- const liveCodingArtifact = formatCodingArtifact(frame.codingSessions?.[0]);
219
+ const liveCodingArtifact = formatCodingArtifact(liveCodingSession);
182
220
  if (liveCodingArtifact) {
183
221
  return liveCodingArtifact;
184
222
  }
@@ -400,6 +438,43 @@ function suggestBridgeForActiveWork(input) {
400
438
  function formatSessionLabel(session) {
401
439
  return `${session.channel}/${session.key}`;
402
440
  }
441
+ function deriveResumeHandleConfidence(primaryObligation, codingSession) {
442
+ if (primaryObligation?.currentArtifact?.trim() && primaryObligation.nextAction?.trim())
443
+ return "high";
444
+ if (codingSession?.checkpoint?.trim())
445
+ return "medium";
446
+ if (primaryObligation)
447
+ return "low";
448
+ return "low";
449
+ }
450
+ function buildResumeHandle(currentSession, primaryObligation, codingSessions) {
451
+ const sessionLabel = currentSession ? formatSessionLabel(currentSession) : null;
452
+ if (!sessionLabel)
453
+ return null;
454
+ const liveCoding = codingSessions[0] ?? null;
455
+ const lane = liveCoding
456
+ ? `${formatCodingLaneLabel(liveCoding)}${describeCodingSessionScope(liveCoding, currentSession)}`
457
+ : (primaryObligation?.currentSurface?.label?.trim() || null);
458
+ const artifact = primaryObligation?.currentArtifact?.trim()
459
+ || (liveCoding?.artifactPath?.trim() || null);
460
+ const blockerOrWaitingOn = primaryObligation?.meaning?.waitingOn?.detail?.trim() || null;
461
+ const nextAction = primaryObligation?.nextAction?.trim()
462
+ || (primaryObligation?.content?.trim() ? `work on "${primaryObligation.content.trim()}" and bring back a concrete artifact` : null);
463
+ const lastVerifiedCheckpoint = liveCoding?.checkpoint?.trim() || null;
464
+ const codingIdentity = liveCoding
465
+ ? { sessionId: liveCoding.id, runner: liveCoding.runner, status: liveCoding.status }
466
+ : null;
467
+ return {
468
+ sessionLabel,
469
+ lane,
470
+ artifact,
471
+ blockerOrWaitingOn,
472
+ nextAction,
473
+ lastVerifiedCheckpoint,
474
+ confidence: deriveResumeHandleConfidence(primaryObligation, liveCoding),
475
+ codingIdentity,
476
+ };
477
+ }
403
478
  function buildActiveWorkFrame(input) {
404
479
  const friendSessions = input.currentSession
405
480
  ? input.friendActivity
@@ -418,6 +493,8 @@ function buildActiveWorkFrame(input) {
418
493
  : (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff || openObligations > 0 || liveCodingSessions.length > 0)
419
494
  ? "inward-work"
420
495
  : "local-turn";
496
+ const primaryObligation = selectPrimaryObligation(pendingObligations, input.currentSession ?? null);
497
+ const resumeHandle = buildResumeHandle(input.currentSession ?? null, primaryObligation, liveCodingSessions);
421
498
  const frame = {
422
499
  currentSession: input.currentSession ?? null,
423
500
  currentObligation: input.currentObligation?.trim() || null,
@@ -449,6 +526,8 @@ function buildActiveWorkFrame(input) {
449
526
  taskBoard: input.taskBoard,
450
527
  targetCandidates: input.targetCandidates,
451
528
  }),
529
+ primaryObligation,
530
+ resumeHandle,
452
531
  };
453
532
  (0, runtime_1.emitNervesEvent)({
454
533
  component: "engine",
@@ -510,6 +589,9 @@ function formatActiveWorkFrame(frame, options) {
510
589
  if (nextAction) {
511
590
  lines.push(`- next action: ${nextAction}`);
512
591
  }
592
+ if (frame.resumeHandle?.lastVerifiedCheckpoint) {
593
+ lines.push(`- last checkpoint: ${frame.resumeHandle.lastVerifiedCheckpoint}`);
594
+ }
513
595
  }
514
596
  // Inner status block
515
597
  const job = frame.inner?.job;
@@ -634,9 +716,111 @@ function formatLiveWorldStateCheckpoint(frame) {
634
716
  `- current artifact: ${currentArtifact}`,
635
717
  `- next action: ${nextAction}`,
636
718
  ];
719
+ if (frame.resumeHandle?.lastVerifiedCheckpoint) {
720
+ lines.push(`- last checkpoint: ${frame.resumeHandle.lastVerifiedCheckpoint}`);
721
+ }
637
722
  if (otherActiveSessions.length > 0) {
638
723
  lines.push("other active sessions:");
639
724
  lines.push(...otherActiveSessions);
640
725
  }
641
726
  return lines.join("\n");
642
727
  }
728
+ function snapshotActiveWork(frame) {
729
+ return {
730
+ obligationSnapshots: (frame.pendingObligations ?? []).map((ob) => ({
731
+ id: ob.id,
732
+ status: ob.status,
733
+ artifact: ob.currentArtifact?.trim() || null,
734
+ nextAction: ob.nextAction?.trim() || null,
735
+ })),
736
+ codingSnapshots: (frame.codingSessions ?? []).map((cs) => ({
737
+ id: cs.id,
738
+ status: cs.status,
739
+ artifact: cs.artifactPath?.trim() || null,
740
+ checkpoint: cs.checkpoint?.trim() || null,
741
+ })),
742
+ timestamp: new Date().toISOString(),
743
+ };
744
+ }
745
+ function detectActiveWorkChanges(previous, current) {
746
+ const changes = [];
747
+ const prevObMap = new Map(previous.obligationSnapshots.map((ob) => [ob.id, ob]));
748
+ const currObMap = new Map(current.obligationSnapshots.map((ob) => [ob.id, ob]));
749
+ // Detect new and changed obligations
750
+ for (const [id, curr] of currObMap) {
751
+ const prev = prevObMap.get(id);
752
+ if (!prev) {
753
+ changes.push({
754
+ kind: "obligation_appeared",
755
+ id,
756
+ from: null,
757
+ to: curr.status,
758
+ summary: `new obligation: ${curr.status}`,
759
+ });
760
+ continue;
761
+ }
762
+ if (prev.status !== curr.status) {
763
+ changes.push({
764
+ kind: "obligation_status_changed",
765
+ id,
766
+ from: prev.status,
767
+ to: curr.status,
768
+ summary: `obligation ${prev.status} -> ${curr.status}`,
769
+ });
770
+ }
771
+ if (prev.artifact !== curr.artifact) {
772
+ changes.push({
773
+ kind: "obligation_artifact_changed",
774
+ id,
775
+ from: prev.artifact,
776
+ to: curr.artifact,
777
+ summary: curr.artifact ? `artifact updated: ${curr.artifact}` : "artifact cleared",
778
+ });
779
+ }
780
+ }
781
+ const prevCodingMap = new Map(previous.codingSnapshots.map((cs) => [cs.id, cs]));
782
+ const currCodingMap = new Map(current.codingSnapshots.map((cs) => [cs.id, cs]));
783
+ for (const [id, curr] of currCodingMap) {
784
+ const prev = prevCodingMap.get(id);
785
+ if (!prev)
786
+ continue;
787
+ if (prev.status !== curr.status) {
788
+ changes.push({
789
+ kind: "coding_status_changed",
790
+ id,
791
+ from: prev.status,
792
+ to: curr.status,
793
+ summary: `coding ${prev.status} -> ${curr.status}`,
794
+ });
795
+ }
796
+ if (prev.artifact !== curr.artifact) {
797
+ changes.push({
798
+ kind: "coding_artifact_changed",
799
+ id,
800
+ from: prev.artifact,
801
+ to: curr.artifact,
802
+ summary: curr.artifact ? `artifact updated: ${curr.artifact}` : "artifact cleared",
803
+ });
804
+ }
805
+ if (prev.checkpoint !== curr.checkpoint) {
806
+ changes.push({
807
+ kind: "coding_checkpoint_changed",
808
+ id,
809
+ from: prev.checkpoint,
810
+ to: curr.checkpoint,
811
+ summary: curr.checkpoint ? `checkpoint: ${curr.checkpoint}` : "checkpoint cleared",
812
+ });
813
+ }
814
+ }
815
+ return changes;
816
+ }
817
+ function formatActiveWorkChanges(changes) {
818
+ if (changes.length === 0)
819
+ return "";
820
+ const lines = ["## what changed since last looked"];
821
+ for (const change of changes) {
822
+ lines.push(`- ${change.summary}`);
823
+ }
824
+ return lines.join("\n");
825
+ }
826
+ /* v8 ignore stop */
@@ -20,9 +20,15 @@ function deriveCommitments(activeWorkFrame, innerJob, pendingObligations) {
20
20
  const completionCriteria = [];
21
21
  const safeToIgnore = [];
22
22
  // Persistent obligations from the obligation store
23
+ // Sort by status priority: investigating/waiting/updating before pending
23
24
  if (pendingObligations && pendingObligations.length > 0) {
25
+ const sorted = [...pendingObligations].sort((a, b) => {
26
+ const advancedA = a.status !== "pending" && a.status !== "fulfilled" ? 0 : 1;
27
+ const advancedB = b.status !== "pending" && b.status !== "fulfilled" ? 0 : 1;
28
+ return advancedA - advancedB;
29
+ });
24
30
  let hasAdvancedObligation = false;
25
- for (const ob of pendingObligations) {
31
+ for (const ob of sorted) {
26
32
  if (!(0, obligations_1.isOpenObligationStatus)(ob.status))
27
33
  continue;
28
34
  committedTo.push(describeActiveObligation(ob));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.187",
3
+ "version": "0.1.0-alpha.189",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",