@ouro.bot/cli 0.1.0-alpha.724 → 0.1.0-alpha.725

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 (54) hide show
  1. package/changelog.json +12 -0
  2. package/dist/heart/active-work.js +10 -3
  3. package/dist/heart/core.js +343 -77
  4. package/dist/heart/daemon/cli-exec.js +72 -14
  5. package/dist/heart/daemon/cli-help.js +13 -2
  6. package/dist/heart/daemon/cli-parse.js +82 -1
  7. package/dist/heart/daemon/daemon.js +286 -33
  8. package/dist/heart/daemon/doctor.js +21 -6
  9. package/dist/heart/daemon/habit-private-wake.js +6 -1
  10. package/dist/heart/habits/habit-cancel.js +810 -0
  11. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  12. package/dist/heart/habits/habit-migration.js +28 -23
  13. package/dist/heart/habits/habit-parser.js +72 -8
  14. package/dist/heart/habits/habit-runtime-state.js +3 -24
  15. package/dist/heart/habits/habit-scheduler.js +268 -50
  16. package/dist/heart/habits/habit-session.js +16 -0
  17. package/dist/heart/hatch/hatch-flow.js +6 -4
  18. package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
  19. package/dist/heart/orientation-frame.js +58 -1
  20. package/dist/heart/provider-attempt.js +4 -1
  21. package/dist/heart/providers/anthropic.js +23 -2
  22. package/dist/heart/start-of-turn-packet.js +14 -11
  23. package/dist/heart/streaming.js +565 -185
  24. package/dist/mind/obligation-steering.js +15 -9
  25. package/dist/mind/prompt.js +58 -26
  26. package/dist/nerves/coverage/run-artifacts.js +55 -35
  27. package/dist/nerves/review/cli.js +2 -2
  28. package/dist/nerves/review/core.js +14 -7
  29. package/dist/repertoire/tools-base.js +2 -0
  30. package/dist/repertoire/tools-habits.js +103 -0
  31. package/dist/repertoire/tools.js +18 -3
  32. package/dist/rsvp/cli.js +126 -20
  33. package/dist/rsvp/diagnostics.js +12 -2
  34. package/dist/rsvp/habit-policy.js +7 -0
  35. package/dist/rsvp/habit-stage.js +19 -4
  36. package/dist/rsvp/native-habit-runner.js +151 -11
  37. package/dist/rsvp/outbound-state.js +360 -0
  38. package/dist/senses/bluebubbles/client.js +90 -17
  39. package/dist/senses/bluebubbles/inbound-log.js +1 -0
  40. package/dist/senses/bluebubbles/index.js +662 -425
  41. package/dist/senses/bluebubbles/model.js +57 -13
  42. package/dist/senses/bluebubbles/mutation-log.js +21 -10
  43. package/dist/senses/bluebubbles/reaction-policy.js +55 -0
  44. package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
  45. package/dist/senses/cli/tui-store.js +1 -0
  46. package/dist/senses/cli.js +1 -0
  47. package/dist/senses/habit-lifecycle-guard.js +24 -0
  48. package/dist/senses/ingress-evidence.js +2 -0
  49. package/dist/senses/pipeline.js +90 -24
  50. package/dist/senses/private-runtime-worker.js +21 -20
  51. package/dist/senses/private-runtime.js +79 -29
  52. package/dist/senses/shared-turn.js +1 -0
  53. package/dist/senses/teams.js +1 -0
  54. package/package.json +6 -1
@@ -10,6 +10,12 @@ exports.renderExactStatusReplyContract = renderExactStatusReplyContract;
10
10
  const config_1 = require("../heart/config");
11
11
  const active_work_1 = require("../heart/active-work");
12
12
  const runtime_1 = require("../nerves/runtime");
13
+ const orientation_frame_1 = require("../heart/orientation-frame");
14
+ function labelSteeringSurface(surface, options) {
15
+ return typeof options?.resumePriorWork === "boolean"
16
+ ? (0, orientation_frame_1.labelPriorWorkSurface)(surface, options.resumePriorWork)
17
+ : surface;
18
+ }
13
19
  function findActivePersistentObligation(frame) {
14
20
  if (!frame)
15
21
  return null;
@@ -47,7 +53,7 @@ function findCurrentSessionStatusObligation(frame) {
47
53
  .sort(newestObligationFirst);
48
54
  return openObligations.find((obligation) => matchesCurrentSession(frame, obligation)) ?? null;
49
55
  }
50
- function renderActiveObligationSteering(obligation) {
56
+ function renderActiveObligationSteering(obligation, options) {
51
57
  (0, runtime_1.emitNervesEvent)({
52
58
  component: "mind",
53
59
  event: "mind.obligation_steering_rendered",
@@ -63,10 +69,10 @@ function renderActiveObligationSteering(obligation) {
63
69
  const surfaceLine = obligation.currentSurface?.label
64
70
  ? `\nright now that work is happening in ${obligation.currentSurface.label}.`
65
71
  : "";
66
- return `## where my attention is
72
+ return labelSteeringSurface(`## where my attention is
67
73
  i'm already working on something i owe ${name}.${surfaceLine}
68
74
 
69
- i should close that loop before i act like this is a fresh blank turn.`;
75
+ i should close that loop before i act like this is a fresh blank turn.`, options);
70
76
  }
71
77
  function mergeArtifactFallback(obligation) {
72
78
  const trimmed = obligation.content.trim();
@@ -141,7 +147,7 @@ function formatNextAction(frame, obligation) {
141
147
  }
142
148
  return obligation ? "continue the active loop and bring the result back here" : "";
143
149
  }
144
- function renderConcreteStatusGuidance(frame, obligation) {
150
+ function renderConcreteStatusGuidance(frame, obligation, options) {
145
151
  const activeLane = obligation ? formatActiveLane(frame, obligation) : "";
146
152
  const currentArtifact = formatCurrentArtifact(frame, obligation);
147
153
  const nextAction = formatNextAction(frame, obligation);
@@ -150,19 +156,19 @@ function renderConcreteStatusGuidance(frame, obligation) {
150
156
  : "";
151
157
  if (!activeLane && !currentArtifact && !nextAction)
152
158
  return "";
153
- return `if someone asks what i'm doing or for status mid-task, i answer from these live facts instead of copying a canned block.
159
+ return labelSteeringSurface(`if someone asks what i'm doing or for status mid-task, i answer from these live facts instead of copying a canned block.
154
160
  the live conversation is ${liveConversation || "not in a live conversation"}.
155
161
  the active lane is ${activeLane}.
156
162
  the current artifact is ${currentArtifact}.
157
163
  if i just finished or verified something concrete in this live lane, i name that as the latest checkpoint.
158
164
  the next action is ${nextAction}.
159
165
 
160
- i answer naturally from those facts instead of forcing a canned status block.`;
166
+ i answer naturally from those facts instead of forcing a canned status block.`, options);
161
167
  }
162
- function renderLiveThreadStatusShape(frame) {
168
+ function renderLiveThreadStatusShape(frame, options) {
163
169
  if (!frame.currentSession)
164
170
  return "";
165
- return `if someone asks what i'm doing or for status mid-task in this live thread, i answer in these exact lines, in order, with no intro paragraph:
171
+ return labelSteeringSurface(`if someone asks what i'm doing or for status mid-task in this live thread, i answer in these exact lines, in order, with no intro paragraph:
166
172
  live conversation: ${frame.currentSession.channel}/${frame.currentSession.key}
167
173
  active lane: this same thread
168
174
  current artifact: <actual artifact or "no artifact yet">
@@ -172,7 +178,7 @@ next action: <smallest concrete next step i'm taking now>
172
178
  no recap paragraph before those lines.
173
179
  no option list.
174
180
  present tense only.
175
- if a finished step matters, i label it "just finished" instead of presenting it as current work.`;
181
+ if a finished step matters, i label it "just finished" instead of presenting it as current work.`, options);
176
182
  }
177
183
  function buildExactStatusReply(frame, obligation, latestCheckpoint, statusCheckScope) {
178
184
  const headerObligation = statusCheckScope === "all-sessions-family"
@@ -767,26 +767,56 @@ function bridgeContextSection(options) {
767
767
  const bridgeContext = options?.bridgeContext?.trim() ?? "";
768
768
  if (!bridgeContext)
769
769
  return "";
770
- return bridgeContext.startsWith("## ") ? bridgeContext : `## active bridge work\n${bridgeContext}`;
770
+ const surface = bridgeContext.startsWith("## ") ? bridgeContext : `## active bridge work\n${bridgeContext}`;
771
+ return (0, orientation_frame_1.labelPriorWorkSurface)(surface, options?.resumePriorWork === true);
771
772
  }
772
773
  function startOfTurnPacketSection(options) {
773
- return options?.startOfTurnPacket ?? "";
774
+ const packet = options?.startOfTurnPacket ?? "";
775
+ if (!packet)
776
+ return "";
777
+ const instruction = options?.resumePriorWork === true
778
+ ? orientation_frame_1.EXPLICIT_PRIOR_WORK_RESUME_INSTRUCTION
779
+ : orientation_frame_1.BACKGROUND_ONLY_INSTRUCTION;
780
+ const lines = packet.split("\n");
781
+ if (lines.some((line) => line === orientation_frame_1.BACKGROUND_ONLY_INSTRUCTION || line === orientation_frame_1.EXPLICIT_PRIOR_WORK_RESUME_INSTRUCTION)) {
782
+ return lines
783
+ .map((line) => line === orientation_frame_1.BACKGROUND_ONLY_INSTRUCTION || line === orientation_frame_1.EXPLICIT_PRIOR_WORK_RESUME_INSTRUCTION
784
+ ? instruction
785
+ : line)
786
+ .join("\n");
787
+ }
788
+ return (0, orientation_frame_1.labelPriorWorkSurface)(packet, options?.resumePriorWork === true);
774
789
  }
775
790
  function arcResumeSection(options) {
776
- return options?.flightRecorderResume ? (0, flight_recorder_1.formatFlightRecorderResume)(options.flightRecorderResume) : "";
777
- }
778
- function orientationFrameSection(options) {
779
- return options?.orientationFrame ? (0, orientation_frame_1.renderOrientationFrame)(options.orientationFrame) : "";
791
+ if (!options?.flightRecorderResume)
792
+ return "";
793
+ return (0, orientation_frame_1.labelPriorWorkSurface)((0, flight_recorder_1.formatFlightRecorderResume)(options.flightRecorderResume), options.resumePriorWork === true);
794
+ }
795
+ function currentTriggerSection(channel, options) {
796
+ const frame = options?.orientationFrame ?? {
797
+ schemaVersion: 1,
798
+ channel,
799
+ currentUserSpeech: [],
800
+ priorAssistantReferents: [],
801
+ signals: [],
802
+ actionPolicy: { mode: "normal" },
803
+ };
804
+ return (0, orientation_frame_1.renderOrientationFrame)(frame);
780
805
  }
781
806
  function activeWorkSection(options) {
782
807
  if (!options?.activeWorkFrame)
783
808
  return "";
784
- return (0, active_work_1.formatActiveWorkFrame)(options.activeWorkFrame, { obligationDetailsRenderedElsewhere: !!options?.startOfTurnPacket });
809
+ return (0, active_work_1.formatActiveWorkFrame)(options.activeWorkFrame, {
810
+ obligationDetailsRenderedElsewhere: !!options?.startOfTurnPacket,
811
+ resumePriorWork: options.resumePriorWork === true,
812
+ });
785
813
  }
786
814
  function liveWorldStateSection(options) {
787
815
  if (!options?.activeWorkFrame)
788
816
  return "";
789
- return (0, active_work_1.formatLiveWorldStateCheckpoint)(options.activeWorkFrame);
817
+ return (0, active_work_1.formatLiveWorldStateCheckpoint)(options.activeWorkFrame, {
818
+ resumePriorWork: options.resumePriorWork === true,
819
+ });
790
820
  }
791
821
  function pendingMessagesSection(options) {
792
822
  const pending = options?.pendingMessages;
@@ -936,13 +966,13 @@ function familyCrossSessionTruthSection(context, options) {
936
966
  return "";
937
967
  // When start-of-turn packet is present, compress to one line
938
968
  if (options?.startOfTurnPacket) {
939
- return "When family asks whole-self status, answer from the cross-session picture above.";
969
+ return (0, orientation_frame_1.labelPriorWorkSurface)("When family asks whole-self status, answer from the cross-session picture above.", options.resumePriorWork === true);
940
970
  }
941
- return `## cross-session truth
971
+ return (0, orientation_frame_1.labelPriorWorkSurface)(`## cross-session truth
942
972
  When family asks what I'm up to or how things are going, I answer from the live world-state across visible sessions and lanes, not just the current thread.
943
973
  When live state conflicts with older transcript history, live state wins.
944
974
  I say what I can see, what is active, and what the next concrete step is.
945
- If part of the picture is still unclear, I say so plainly and note what still needs checking.`;
975
+ If part of the picture is still unclear, I say so plainly and note what still needs checking.`, options.resumePriorWork === true);
946
976
  }
947
977
  function centerOfGravitySteeringSection(channel, options, context) {
948
978
  if (channel === "inner")
@@ -951,19 +981,21 @@ function centerOfGravitySteeringSection(channel, options, context) {
951
981
  if (!frame)
952
982
  return "";
953
983
  const cog = frame.centerOfGravity;
984
+ const labelSurface = (surface) => (0, orientation_frame_1.labelPriorWorkSurface)(surface, options?.resumePriorWork === true);
985
+ const steeringOptions = { resumePriorWork: options?.resumePriorWork === true };
954
986
  const job = frame.inner?.job;
955
987
  const activeObligation = (0, obligation_steering_1.findActivePersistentObligation)(frame);
956
988
  const statusObligation = (0, obligation_steering_1.findStatusObligation)(frame);
957
- const genericConcreteStatus = (0, obligation_steering_1.renderConcreteStatusGuidance)(frame, statusObligation);
989
+ const genericConcreteStatus = (0, obligation_steering_1.renderConcreteStatusGuidance)(frame, statusObligation, steeringOptions);
958
990
  const liveWorldClause = context?.friend?.trustLevel === "family"
959
991
  ? "\nmy center of gravity lives in the active-work world-state above. private-runtime work is one lane inside it, not the whole picture.\nwhen that world-state conflicts with older transcript history, the world-state wins."
960
992
  : "";
961
993
  if (cog === "local-turn") {
962
- return genericConcreteStatus || (0, obligation_steering_1.renderLiveThreadStatusShape)(frame);
994
+ return genericConcreteStatus || (0, obligation_steering_1.renderLiveThreadStatusShape)(frame, steeringOptions);
963
995
  }
964
996
  if (cog === "inward-work") {
965
997
  if (activeObligation) {
966
- return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)}${liveWorldClause}
998
+ return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation, steeringOptions)}${liveWorldClause}
967
999
 
968
1000
  ${genericConcreteStatus}`;
969
1001
  }
@@ -974,21 +1006,21 @@ ${genericConcreteStatus}`;
974
1006
  const obligationClause = job.obligationStatus === "pending"
975
1007
  ? "\ni still owe them an answer."
976
1008
  : "";
977
- return `## where my attention is
1009
+ return labelSurface(`## where my attention is
978
1010
  i'm thinking through something privately right now.${originClause}${obligationClause}${liveWorldClause}
979
1011
 
980
1012
  if this conversation connects to that private-runtime work, i can weave them together.
981
- if it's separate, i can be fully present here -- my private-runtime work will wait.`;
1013
+ if it's separate, i can be fully present here -- my private-runtime work will wait.`);
982
1014
  }
983
1015
  /* v8 ignore start -- surfaced/idle/shared branches tested in prompt-steering.test.ts; CI module caching prevents attribution @preserve */
984
1016
  if (job?.status === "surfaced") {
985
1017
  const originClause = job.origin
986
1018
  ? ` this started when ${job.origin.friendName ?? job.origin.friendId} asked about something.`
987
1019
  : "";
988
- return `## where my attention is
1020
+ return labelSurface(`## where my attention is
989
1021
  i've been thinking privately and reached something.${originClause}${liveWorldClause}
990
1022
 
991
- i should bring my answer back to the conversation it came from.`;
1023
+ i should bring my answer back to the conversation it came from.`);
992
1024
  }
993
1025
  const liveCodingSession = frame.codingSessions?.[0];
994
1026
  if (liveCodingSession) {
@@ -1009,25 +1041,25 @@ after i name this lane, i widen back out with:
1009
1041
  other active sessions:
1010
1042
  ${otherSessionLines.length > 0 ? otherSessionLines.join("\n") : "- none"}`
1011
1043
  : "";
1012
- return `## where my attention is
1044
+ return labelSurface(`## where my attention is
1013
1045
  i already have coding work running in ${liveCodingSession.runner} ${liveCodingSession.id}${scopeClause}.${familyStatusClause}${liveWorldClause}
1014
1046
 
1015
- i should orient around that live lane first, then decide what still needs to come back here.`;
1047
+ i should orient around that live lane first, then decide what still needs to come back here.`);
1016
1048
  }
1017
1049
  if (genericConcreteStatus) {
1018
1050
  return genericConcreteStatus;
1019
1051
  }
1020
- return `## where my attention is
1052
+ return labelSurface(`## where my attention is
1021
1053
  i have unfinished work that needs attention before i move on.
1022
1054
 
1023
- i can take it inward with ponder to think privately, or address it directly here.`;
1055
+ i can take it inward with ponder to think privately, or address it directly here.`);
1024
1056
  }
1025
1057
  if (cog === "shared-work") {
1026
1058
  /* v8 ignore stop */
1027
- return `## where my attention is
1059
+ return labelSurface(`## where my attention is
1028
1060
  this work touches multiple conversations -- i'm holding threads across sessions.${liveWorldClause}
1029
1061
 
1030
- i should keep the different sides aligned. what i learn here may matter there, and vice versa.`;
1062
+ i should keep the different sides aligned. what i learn here may matter there, and vice versa.`);
1031
1063
  }
1032
1064
  /* v8 ignore next -- unreachable: all center-of-gravity modes covered above @preserve */
1033
1065
  return "";
@@ -1051,7 +1083,7 @@ function commitmentsSection(options) {
1051
1083
  const commitments = (0, commitments_1.deriveCommitments)(options.activeWorkFrame, job, options.activeWorkFrame.pendingObligations, awaits);
1052
1084
  if (commitments.committedTo.length === 0 && awaits.length === 0)
1053
1085
  return "";
1054
- return `## my commitments\n\n${(0, commitments_1.formatCommitments)(commitments)}`;
1086
+ return (0, orientation_frame_1.labelPriorWorkSurface)(`## my commitments\n\n${(0, commitments_1.formatCommitments)(commitments)}`, options.resumePriorWork === true);
1055
1087
  }
1056
1088
  const DELEGATION_REASON_PROSE_HINT = {
1057
1089
  explicit_reflection: "something here calls for reflection",
@@ -1451,8 +1483,8 @@ async function buildSystem(channel = "cli", options, context) {
1451
1483
  rhythmStatusSection(options?.daemonHealth),
1452
1484
  // Group 7: dynamic state for this turn
1453
1485
  "# dynamic state for this turn",
1486
+ currentTriggerSection(channel, options),
1454
1487
  startOfTurnPacketSection(options),
1455
- orientationFrameSection(options),
1456
1488
  pulseSection(channel),
1457
1489
  tripLedgerTruthSection(channel, context),
1458
1490
  liveWorldStateSection(options),
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.REPO_SLUG = void 0;
4
4
  exports.getTestRunsRoot = getTestRunsRoot;
5
+ exports.coverageRunOwner = coverageRunOwner;
6
+ exports.getOwnedTestRunsRoot = getOwnedTestRunsRoot;
5
7
  exports.createRunId = createRunId;
6
8
  exports.getRunDir = getRunDir;
7
9
  exports.writeActiveRun = writeActiveRun;
@@ -10,68 +12,86 @@ exports.clearActiveRun = clearActiveRun;
10
12
  exports.writeLatestRun = writeLatestRun;
11
13
  exports.readLatestRun = readLatestRun;
12
14
  const fs_1 = require("fs");
13
- const path_1 = require("path");
15
+ const crypto_1 = require("crypto");
14
16
  const os_1 = require("os");
17
+ const path_1 = require("path");
15
18
  exports.REPO_SLUG = "ouroboros-agent-harness";
16
19
  function getTestRunsRoot(repoSlug = exports.REPO_SLUG) {
17
20
  return (0, path_1.join)((0, os_1.tmpdir)(), "ouroboros-test-runs", repoSlug);
18
21
  }
22
+ function coverageRunOwner(cwd = process.cwd()) {
23
+ const hash = (0, crypto_1.createHash)("sha256").update((0, path_1.resolve)(cwd)).digest("hex").slice(0, 12);
24
+ return `cwd-${hash}`;
25
+ }
26
+ function getOwnedTestRunsRoot(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
27
+ return (0, path_1.join)(getTestRunsRoot(repoSlug), coverageRunOwner(cwd));
28
+ }
19
29
  function createRunId(now = new Date()) {
20
30
  return now.toISOString().replace(/[:.]/g, "-");
21
31
  }
22
- function getRunDir(runId, repoSlug = exports.REPO_SLUG) {
23
- return (0, path_1.join)(getTestRunsRoot(repoSlug), runId);
32
+ function getRunDir(runId, repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
33
+ return (0, path_1.join)(getOwnedTestRunsRoot(repoSlug, cwd), runId);
24
34
  }
25
- function getActiveRunPath(repoSlug = exports.REPO_SLUG) {
26
- return (0, path_1.join)(getTestRunsRoot(repoSlug), ".active-run.json");
35
+ function getActiveRunPath(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
36
+ return (0, path_1.join)(getOwnedTestRunsRoot(repoSlug, cwd), ".active-run.json");
27
37
  }
28
- function getLatestRunPath(repoSlug = exports.REPO_SLUG) {
29
- return (0, path_1.join)(getTestRunsRoot(repoSlug), "latest-run.json");
38
+ function getLatestRunPath(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
39
+ return (0, path_1.join)(getOwnedTestRunsRoot(repoSlug, cwd), "latest-run.json");
30
40
  }
31
- function ensureRoot(repoSlug = exports.REPO_SLUG) {
32
- const root = getTestRunsRoot(repoSlug);
41
+ function ensureRoot(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
42
+ const root = getOwnedTestRunsRoot(repoSlug, cwd);
33
43
  (0, fs_1.mkdirSync)(root, { recursive: true });
34
44
  return root;
35
45
  }
36
- function writeActiveRun(info) {
37
- ensureRoot(info.repo_slug);
38
- (0, fs_1.writeFileSync)(getActiveRunPath(info.repo_slug), JSON.stringify(info, null, 2), "utf8");
46
+ function isValidRunInfo(value, repoSlug, cwd) {
47
+ if (typeof value !== "object" || value === null)
48
+ return false;
49
+ const parsed = value;
50
+ const owner = coverageRunOwner(cwd);
51
+ if (parsed.repo_slug !== repoSlug ||
52
+ parsed.run_owner !== owner ||
53
+ typeof parsed.run_id !== "string" ||
54
+ parsed.run_id.length === 0 ||
55
+ typeof parsed.run_dir !== "string" ||
56
+ parsed.run_dir.length === 0 ||
57
+ typeof parsed.created_at !== "string" ||
58
+ parsed.created_at.length === 0) {
59
+ return false;
60
+ }
61
+ const ownerRoot = (0, path_1.resolve)(getOwnedTestRunsRoot(repoSlug, cwd));
62
+ const runDir = (0, path_1.resolve)(parsed.run_dir);
63
+ const relativeRunDir = (0, path_1.relative)(ownerRoot, runDir);
64
+ const isContained = relativeRunDir !== "" && relativeRunDir !== ".." && !relativeRunDir.startsWith(`..${path_1.sep}`) && !(0, path_1.isAbsolute)(relativeRunDir);
65
+ return isContained && runDir === (0, path_1.resolve)(ownerRoot, parsed.run_id);
39
66
  }
40
- function readActiveRun(repoSlug = exports.REPO_SLUG) {
41
- const filePath = getActiveRunPath(repoSlug);
67
+ function readRunInfo(filePath, repoSlug, cwd) {
42
68
  if (!(0, fs_1.existsSync)(filePath))
43
69
  return null;
44
70
  try {
45
71
  const parsed = JSON.parse((0, fs_1.readFileSync)(filePath, "utf8"));
46
- if (!parsed.run_id || !parsed.run_dir)
47
- return null;
48
- return parsed;
72
+ return isValidRunInfo(parsed, repoSlug, cwd) ? parsed : null;
49
73
  }
50
74
  catch {
51
75
  return null;
52
76
  }
53
77
  }
54
- function clearActiveRun(repoSlug = exports.REPO_SLUG) {
55
- const filePath = getActiveRunPath(repoSlug);
78
+ function writeActiveRun(info, cwd = process.cwd()) {
79
+ ensureRoot(info.repo_slug, cwd);
80
+ (0, fs_1.writeFileSync)(getActiveRunPath(info.repo_slug, cwd), JSON.stringify(info, null, 2), "utf8");
81
+ }
82
+ function readActiveRun(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
83
+ return readRunInfo(getActiveRunPath(repoSlug, cwd), repoSlug, cwd);
84
+ }
85
+ function clearActiveRun(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
86
+ const filePath = getActiveRunPath(repoSlug, cwd);
56
87
  if ((0, fs_1.existsSync)(filePath)) {
57
88
  (0, fs_1.unlinkSync)(filePath);
58
89
  }
59
90
  }
60
- function writeLatestRun(info) {
61
- ensureRoot(info.repo_slug);
62
- (0, fs_1.writeFileSync)(getLatestRunPath(info.repo_slug), JSON.stringify(info, null, 2), "utf8");
91
+ function writeLatestRun(info, cwd = process.cwd()) {
92
+ ensureRoot(info.repo_slug, cwd);
93
+ (0, fs_1.writeFileSync)(getLatestRunPath(info.repo_slug, cwd), JSON.stringify(info, null, 2), "utf8");
63
94
  }
64
- function readLatestRun(repoSlug = exports.REPO_SLUG) {
65
- const filePath = getLatestRunPath(repoSlug);
66
- if (!(0, fs_1.existsSync)(filePath))
67
- return null;
68
- try {
69
- const parsed = JSON.parse((0, fs_1.readFileSync)(filePath, "utf8"));
70
- if (!parsed.run_id || !parsed.run_dir)
71
- return null;
72
- return parsed;
73
- }
74
- catch {
75
- return null;
76
- }
95
+ function readLatestRun(repoSlug = exports.REPO_SLUG, cwd = process.cwd()) {
96
+ return readRunInfo(getLatestRunPath(repoSlug, cwd), repoSlug, cwd);
77
97
  }
@@ -38,7 +38,7 @@ const path = __importStar(require("node:path"));
38
38
  const identity_1 = require("../../heart/identity");
39
39
  const core_1 = require("./core");
40
40
  function parseArgs(argv) {
41
- const args = { process: "daemon", json: false, help: false };
41
+ const args = { process: core_1.DEFAULT_NERVES_PROCESS, json: false, help: false };
42
42
  for (let i = 0; i < argv.length; i++) {
43
43
  const token = argv[i];
44
44
  const next = argv[i + 1];
@@ -100,7 +100,7 @@ function printHelp() {
100
100
  "Tail the agent's nerves ndjson and filter recent events. Read-only.",
101
101
  "",
102
102
  "options:",
103
- " --process <name> log stream to read (default: daemon)",
103
+ ` --process <name> log stream to read (default: ${core_1.DEFAULT_NERVES_PROCESS})`,
104
104
  " --agent <name> agent bundle to read from (default: current)",
105
105
  " --component <substr> filter by component substring (case-insensitive)",
106
106
  " --event <substr> filter by event-name substring (case-insensitive)",
@@ -33,10 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEFAULT_NERVES_PROCESS = void 0;
36
37
  exports.parseDuration = parseDuration;
37
38
  exports.reviewNerveEvents = reviewNerveEvents;
38
39
  exports.formatNerveEntry = formatNerveEntry;
39
40
  const fs = __importStar(require("node:fs"));
41
+ exports.DEFAULT_NERVES_PROCESS = "ouro";
40
42
  const DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
41
43
  function parseDuration(value) {
42
44
  const match = DURATION_PATTERN.exec(value.trim().toLowerCase());
@@ -56,15 +58,20 @@ function parseDuration(value) {
56
58
  /* v8 ignore stop */
57
59
  }
58
60
  }
59
- function entryTimeMs(parsed) {
61
+ function entryTimestamp(parsed) {
60
62
  if (!parsed)
61
63
  return null;
62
- const time = parsed.time;
63
- if (typeof time === "string") {
64
- const ms = Date.parse(time);
65
- return Number.isFinite(ms) ? ms : null;
64
+ if (Object.prototype.hasOwnProperty.call(parsed, "ts")) {
65
+ return typeof parsed.ts === "string" ? parsed.ts : null;
66
66
  }
67
- return null;
67
+ return typeof parsed.time === "string" ? parsed.time : null;
68
+ }
69
+ function entryTimeMs(parsed) {
70
+ const timestamp = entryTimestamp(parsed);
71
+ if (timestamp === null)
72
+ return null;
73
+ const ms = Date.parse(timestamp);
74
+ return Number.isFinite(ms) ? ms : null;
68
75
  }
69
76
  function readLastNLines(filePath, maxLines, maxBytes = 8 * 1024 * 1024) {
70
77
  let text;
@@ -143,7 +150,7 @@ function formatNerveEntry(entry) {
143
150
  const parsed = entry.parsed;
144
151
  if (!parsed)
145
152
  return entry.raw;
146
- const time = String(parsed.time ?? "");
153
+ const time = entryTimestamp(parsed) ?? "";
147
154
  const level = String(parsed.level ?? "info");
148
155
  const component = String(parsed.component ?? "?");
149
156
  const event = String(parsed.event ?? "?");
@@ -27,6 +27,7 @@ const tools_evolution_1 = require("./tools-evolution");
27
27
  const tools_runtime_1 = require("./tools-runtime");
28
28
  const tools_orientation_1 = require("./tools-orientation");
29
29
  const tools_rsvp_1 = require("./tools-rsvp");
30
+ const tools_habits_1 = require("./tools-habits");
30
31
  // Re-export flow tools for consumers that import them from tools-base
31
32
  var tools_flow_1 = require("./tools-flow");
32
33
  Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_flow_1.ponderTool; } });
@@ -69,6 +70,7 @@ exports.baseToolDefinitions = [
69
70
  ...tools_orientation_1.orientationToolDefinitions,
70
71
  ...tools_runtime_1.runtimeToolDefinitions,
71
72
  ...tools_rsvp_1.rsvpToolDefinitions,
73
+ ...tools_habits_1.habitToolDefinitions,
72
74
  ];
73
75
  // Convenience array of just the tool schemas (no handler/integration metadata).
74
76
  // Used by consumers that need the OpenAI function-tool format.
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.habitToolDefinitions = void 0;
4
+ const friends_1 = require("@ouro.bot/friends");
5
+ const runtime_1 = require("../nerves/runtime");
6
+ const habit_cancel_1 = require("../heart/habits/habit-cancel");
7
+ const semantic_receipts_1 = require("../senses/bluebubbles/semantic-receipts");
8
+ const CAPTURE_LOCATOR_PATTERN = /^capture:([a-f0-9]{64})$/;
9
+ const ACTOR_AUTHORITY_ERROR = "habit_cancel requires trusted current-ingress actor authority";
10
+ async function requireTrustedCurrentIngressActor(agentRoot, captureKeyHash, context) {
11
+ const capture = (0, semantic_receipts_1.readBlueBubblesSemanticCaptureAtRoot)(agentRoot, captureKeyHash);
12
+ const actor = capture?.event.actor;
13
+ if (!actor || !context?.friendStore)
14
+ throw new Error(ACTOR_AUTHORITY_ERROR);
15
+ try {
16
+ const friend = await context.friendStore.findByExternalId(actor.provider, actor.externalId);
17
+ if (!friends_1.TRUSTED_LEVELS.has(friend?.trustLevel ?? "stranger")) {
18
+ throw new Error(ACTOR_AUTHORITY_ERROR);
19
+ }
20
+ }
21
+ catch {
22
+ throw new Error(ACTOR_AUTHORITY_ERROR);
23
+ }
24
+ }
25
+ exports.habitToolDefinitions = [
26
+ {
27
+ tool: {
28
+ type: "function",
29
+ function: {
30
+ name: "habit_cancel",
31
+ description: "Cancel a named habit from the immutable evidence capture that triggered the current turn.",
32
+ parameters: {
33
+ type: "object",
34
+ properties: {
35
+ habit: {
36
+ type: "string",
37
+ description: "Exact habit identifier to cancel.",
38
+ },
39
+ evidence: {
40
+ type: "string",
41
+ description: "Current ingress evidence locator in capture:<sha256> form.",
42
+ },
43
+ },
44
+ required: ["habit", "evidence"],
45
+ additionalProperties: false,
46
+ },
47
+ },
48
+ },
49
+ riskProfile: {
50
+ mutates: "durable_state_write",
51
+ risk: "high",
52
+ reason: "Permanently transitions a durable habit definition to cancelled.",
53
+ },
54
+ terminalProjection: {
55
+ mode: "verbatim",
56
+ requiresSoleCall: true,
57
+ clearBufferedText: true,
58
+ },
59
+ summaryKeys: ["habit", "evidence"],
60
+ handler: async (args, context) => {
61
+ (0, runtime_1.emitNervesEvent)({
62
+ component: "tools",
63
+ event: "tools.habit_cancel_invoked",
64
+ message: "habit cancellation tool invoked",
65
+ meta: { habit: typeof args.habit === "string" ? args.habit : null },
66
+ });
67
+ const keys = Object.keys(args);
68
+ const unexpected = keys.find((key) => key !== "habit" && key !== "evidence");
69
+ if (unexpected)
70
+ throw new Error(`unexpected habit_cancel argument: ${unexpected}`);
71
+ if (keys.length !== 2 || typeof args.habit !== "string" || args.habit.trim().length === 0) {
72
+ throw new Error("habit_cancel requires exactly habit and evidence");
73
+ }
74
+ const match = CAPTURE_LOCATOR_PATTERN.exec(args.evidence);
75
+ if (!match)
76
+ throw new Error("habit_cancel evidence must use capture:<sha256>");
77
+ const currentIngressEvidence = context?.currentIngressEvidence;
78
+ if (!currentIngressEvidence
79
+ || Object.keys(currentIngressEvidence).length !== 3
80
+ || Object.keys(currentIngressEvidence).some((key) => (key !== "schemaVersion" && key !== "provider" && key !== "captureKeyHash"))
81
+ || currentIngressEvidence.schemaVersion !== 1
82
+ || currentIngressEvidence.provider !== "bluebubbles"
83
+ || !/^[a-f0-9]{64}$/.test(currentIngressEvidence.captureKeyHash))
84
+ throw new Error("habit_cancel requires authoritative current ingress evidence");
85
+ if (currentIngressEvidence.captureKeyHash !== match[1]) {
86
+ throw new Error("habit_cancel evidence mismatch with current ingress evidence");
87
+ }
88
+ if (!context?.agentRoot)
89
+ throw new Error("habit_cancel requires an agent root");
90
+ await requireTrustedCurrentIngressActor(context.agentRoot, currentIngressEvidence.captureKeyHash, context);
91
+ const receipt = await (0, habit_cancel_1.cancelHabit)({
92
+ agentRoot: context.agentRoot,
93
+ habitId: args.habit,
94
+ evidenceLocator: args.evidence,
95
+ authority: {
96
+ kind: "current_ingress",
97
+ currentIngressEvidence,
98
+ },
99
+ });
100
+ return receipt.acknowledgement;
101
+ },
102
+ },
103
+ ];
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
4
4
  exports.resetMcpDefinitions = resetMcpDefinitions;
5
5
  exports.getToolsForChannel = getToolsForChannel;
6
+ exports.getRestrictedReactionFeedbackTools = getRestrictedReactionFeedbackTools;
7
+ exports.resolveToolDefinition = resolveToolDefinition;
6
8
  exports.shellRiskProfile = shellRiskProfile;
7
9
  exports.riskProfileForTool = riskProfileForTool;
8
10
  exports.riskProfileForToolName = riskProfileForToolName;
@@ -125,11 +127,24 @@ function getToolsForChannel(capabilities, toolPreferences, _context, providerCap
125
127
  }
126
128
  return filterByCapability(result, providerCapabilities);
127
129
  }
128
- // Look up a tool definition from the combined registry (native + MCP).
129
- function findDefinition(toolName) {
130
- return allDefinitions.find((d) => d.tool.function.name === toolName)
130
+ /**
131
+ * The complete provider-facing tool surface for trusted reaction feedback.
132
+ * Keep these schemas sourced from the normal registry so restricted turns
133
+ * cannot drift onto lookalike definitions or inherit channel/MCP tools.
134
+ */
135
+ function getRestrictedReactionFeedbackTools() {
136
+ const orientationGetTool = tools_base_1.baseToolDefinitions.find((definition) => definition.tool.function.name === "orientation_get").tool;
137
+ return [tools_base_1.settleTool, tools_base_1.observeTool, orientationGetTool];
138
+ }
139
+ // Look up a tool definition from the live combined registry (native + MCP).
140
+ // The base registry is intentionally consulted at call time so tests and
141
+ // runtime extensions cannot leave execution metadata behind a stale snapshot.
142
+ function resolveToolDefinition(toolName) {
143
+ return tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === toolName)
144
+ ?? allDefinitions.find((d) => d.tool.function.name === toolName)
131
145
  ?? mcpDefinitions.find((d) => d.tool.function.name === toolName);
132
146
  }
147
+ const findDefinition = resolveToolDefinition;
133
148
  function normalizeGuardArgs(_name, args) {
134
149
  return args;
135
150
  }