@ouro.bot/cli 0.1.0-alpha.9 → 0.1.0-alpha.91

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 (128) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +147 -205
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +536 -0
  7. package/dist/heart/active-work.js +251 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/commitments.js +109 -0
  12. package/dist/heart/config.js +68 -23
  13. package/dist/heart/core.js +452 -93
  14. package/dist/heart/cross-chat-delivery.js +146 -0
  15. package/dist/heart/daemon/agent-discovery.js +81 -0
  16. package/dist/heart/daemon/auth-flow.js +430 -0
  17. package/dist/heart/daemon/daemon-cli.js +1738 -269
  18. package/dist/heart/daemon/daemon-entry.js +55 -6
  19. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  20. package/dist/heart/daemon/daemon.js +216 -10
  21. package/dist/heart/daemon/hatch-animation.js +10 -3
  22. package/dist/heart/daemon/hatch-flow.js +7 -82
  23. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  24. package/dist/heart/daemon/launchd.js +159 -0
  25. package/dist/heart/daemon/log-tailer.js +4 -3
  26. package/dist/heart/daemon/message-router.js +17 -8
  27. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  28. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  29. package/dist/heart/daemon/ouro-entry.js +0 -0
  30. package/dist/heart/daemon/ouro-path-installer.js +260 -0
  31. package/dist/heart/daemon/ouro-uti.js +11 -2
  32. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  33. package/dist/heart/daemon/process-manager.js +14 -1
  34. package/dist/heart/daemon/run-hooks.js +37 -0
  35. package/dist/heart/daemon/runtime-logging.js +58 -15
  36. package/dist/heart/daemon/runtime-metadata.js +219 -0
  37. package/dist/heart/daemon/runtime-mode.js +67 -0
  38. package/dist/heart/daemon/sense-manager.js +307 -0
  39. package/dist/heart/daemon/skill-management-installer.js +94 -0
  40. package/dist/heart/daemon/socket-client.js +202 -0
  41. package/dist/heart/daemon/specialist-orchestrator.js +53 -84
  42. package/dist/heart/daemon/specialist-prompt.js +63 -11
  43. package/dist/heart/daemon/specialist-tools.js +211 -60
  44. package/dist/heart/daemon/staged-restart.js +114 -0
  45. package/dist/heart/daemon/thoughts.js +507 -0
  46. package/dist/heart/daemon/update-checker.js +111 -0
  47. package/dist/heart/daemon/update-hooks.js +138 -0
  48. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  49. package/dist/heart/delegation.js +62 -0
  50. package/dist/heart/identity.js +126 -21
  51. package/dist/heart/kicks.js +1 -19
  52. package/dist/heart/model-capabilities.js +48 -0
  53. package/dist/heart/obligations.js +191 -0
  54. package/dist/heart/progress-story.js +42 -0
  55. package/dist/heart/providers/anthropic.js +74 -9
  56. package/dist/heart/providers/azure.js +86 -7
  57. package/dist/heart/providers/github-copilot.js +149 -0
  58. package/dist/heart/providers/minimax.js +4 -0
  59. package/dist/heart/providers/openai-codex.js +12 -3
  60. package/dist/heart/safe-workspace.js +362 -0
  61. package/dist/heart/sense-truth.js +61 -0
  62. package/dist/heart/session-activity.js +169 -0
  63. package/dist/heart/session-recall.js +116 -0
  64. package/dist/heart/streaming.js +100 -22
  65. package/dist/heart/target-resolution.js +123 -0
  66. package/dist/heart/turn-coordinator.js +28 -0
  67. package/dist/mind/associative-recall.js +14 -2
  68. package/dist/mind/bundle-manifest.js +70 -0
  69. package/dist/mind/context.js +57 -11
  70. package/dist/mind/first-impressions.js +16 -2
  71. package/dist/mind/friends/channel.js +35 -0
  72. package/dist/mind/friends/group-context.js +144 -0
  73. package/dist/mind/friends/store-file.js +19 -0
  74. package/dist/mind/friends/trust-explanation.js +74 -0
  75. package/dist/mind/friends/types.js +8 -0
  76. package/dist/mind/memory.js +27 -26
  77. package/dist/mind/obligation-steering.js +31 -0
  78. package/dist/mind/pending.js +76 -9
  79. package/dist/mind/phrases.js +1 -0
  80. package/dist/mind/prompt.js +467 -77
  81. package/dist/mind/token-estimate.js +8 -12
  82. package/dist/nerves/cli-logging.js +15 -2
  83. package/dist/nerves/coverage/run-artifacts.js +1 -1
  84. package/dist/nerves/index.js +12 -0
  85. package/dist/repertoire/ado-client.js +4 -2
  86. package/dist/repertoire/coding/feedback.js +180 -0
  87. package/dist/repertoire/coding/index.js +4 -1
  88. package/dist/repertoire/coding/manager.js +69 -4
  89. package/dist/repertoire/coding/spawner.js +21 -3
  90. package/dist/repertoire/coding/tools.js +105 -2
  91. package/dist/repertoire/data/ado-endpoints.json +188 -0
  92. package/dist/repertoire/guardrails.js +290 -0
  93. package/dist/repertoire/mcp-client.js +254 -0
  94. package/dist/repertoire/mcp-manager.js +195 -0
  95. package/dist/repertoire/skills.js +3 -26
  96. package/dist/repertoire/tasks/board.js +12 -0
  97. package/dist/repertoire/tasks/index.js +23 -9
  98. package/dist/repertoire/tasks/transitions.js +1 -2
  99. package/dist/repertoire/tools-base.js +714 -249
  100. package/dist/repertoire/tools-bluebubbles.js +93 -0
  101. package/dist/repertoire/tools-teams.js +58 -25
  102. package/dist/repertoire/tools.js +106 -53
  103. package/dist/senses/bluebubbles-client.js +210 -5
  104. package/dist/senses/bluebubbles-entry.js +2 -0
  105. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  106. package/dist/senses/bluebubbles-media.js +339 -0
  107. package/dist/senses/bluebubbles-model.js +12 -4
  108. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  109. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  110. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  111. package/dist/senses/bluebubbles.js +894 -45
  112. package/dist/senses/cli-layout.js +187 -0
  113. package/dist/senses/cli.js +400 -164
  114. package/dist/senses/continuity.js +94 -0
  115. package/dist/senses/debug-activity.js +154 -0
  116. package/dist/senses/inner-dialog-worker.js +47 -18
  117. package/dist/senses/inner-dialog.js +377 -83
  118. package/dist/senses/pipeline.js +307 -0
  119. package/dist/senses/teams.js +573 -129
  120. package/dist/senses/trust-gate.js +112 -2
  121. package/package.json +14 -3
  122. package/subagents/README.md +4 -70
  123. package/dist/heart/daemon/specialist-session.js +0 -142
  124. package/dist/heart/daemon/subagent-installer.js +0 -125
  125. package/dist/inner-worker-entry.js +0 -4
  126. package/subagents/work-doer.md +0 -233
  127. package/subagents/work-merger.md +0 -624
  128. package/subagents/work-planner.md +0 -373
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createGithubCopilotProviderRuntime = createGithubCopilotProviderRuntime;
7
+ const openai_1 = __importDefault(require("openai"));
8
+ const config_1 = require("../config");
9
+ const identity_1 = require("../identity");
10
+ const runtime_1 = require("../../nerves/runtime");
11
+ const streaming_1 = require("../streaming");
12
+ const model_capabilities_1 = require("../model-capabilities");
13
+ /* v8 ignore start -- auth guidance helpers: tested via mock-driven provider tests @preserve */
14
+ function isAuthFailure(error) {
15
+ if (!(error instanceof Error))
16
+ return false;
17
+ const status = error.status;
18
+ return status === 401 || status === 403;
19
+ }
20
+ function getReauthGuidance(reason) {
21
+ const agentName = (0, identity_1.getAgentName)();
22
+ return [
23
+ `provider github-copilot failed (${reason}).`,
24
+ `Run \`ouro auth verify --agent ${agentName}\` to check all configured providers,`,
25
+ `\`ouro auth switch --agent ${agentName} --provider <other>\` to switch,`,
26
+ `or \`ouro auth --agent ${agentName} --provider github-copilot\` to reconfigure.`,
27
+ ].join(" ");
28
+ }
29
+ function withAuthGuidance(error) {
30
+ const base = error instanceof Error ? error.message : String(error);
31
+ if (isAuthFailure(error)) {
32
+ return new Error(getReauthGuidance(base));
33
+ }
34
+ return error instanceof Error ? error : new Error(String(error));
35
+ }
36
+ /* v8 ignore stop */
37
+ function createGithubCopilotProviderRuntime() {
38
+ (0, runtime_1.emitNervesEvent)({
39
+ component: "engine",
40
+ event: "engine.provider_init",
41
+ message: "github-copilot provider init",
42
+ meta: { provider: "github-copilot" },
43
+ });
44
+ const config = (0, config_1.getGithubCopilotConfig)();
45
+ if (!config.githubToken) {
46
+ throw new Error("provider 'github-copilot' is selected in agent.json but providers.github-copilot.githubToken is missing in secrets.json.");
47
+ }
48
+ if (!config.baseUrl) {
49
+ throw new Error("provider 'github-copilot' is selected in agent.json but providers.github-copilot.baseUrl is missing in secrets.json.");
50
+ }
51
+ const isCompletionsModel = config.model.startsWith("claude");
52
+ const modelCaps = (0, model_capabilities_1.getModelCapabilities)(config.model);
53
+ const capabilities = new Set();
54
+ /* v8 ignore next -- branch: capability detection tested via unit test @preserve */
55
+ if (modelCaps.reasoningEffort)
56
+ capabilities.add("reasoning-effort");
57
+ const client = new openai_1.default({
58
+ apiKey: config.githubToken,
59
+ baseURL: config.baseUrl,
60
+ timeout: 30000,
61
+ maxRetries: 0,
62
+ });
63
+ if (isCompletionsModel) {
64
+ // Chat completions path (Claude models via Copilot)
65
+ return {
66
+ id: "github-copilot",
67
+ model: config.model,
68
+ client,
69
+ capabilities,
70
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
71
+ resetTurnState(_messages) {
72
+ // No provider-owned turn state for chat-completions path.
73
+ },
74
+ appendToolOutput(_callId, _output) {
75
+ // Chat-completions providers rely on canonical messages only.
76
+ },
77
+ /* v8 ignore start -- streamTurn: tested via mock assertions in github-copilot.test.ts @preserve */
78
+ async streamTurn(request) {
79
+ const params = {
80
+ messages: request.messages,
81
+ tools: request.activeTools,
82
+ stream: true,
83
+ };
84
+ if (this.model)
85
+ params.model = this.model;
86
+ if (request.traceId)
87
+ params.metadata = { trace_id: request.traceId };
88
+ if (request.toolChoiceRequired)
89
+ params.tool_choice = "required";
90
+ try {
91
+ return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal);
92
+ }
93
+ catch (error) {
94
+ throw withAuthGuidance(error);
95
+ }
96
+ },
97
+ /* v8 ignore stop */
98
+ };
99
+ }
100
+ // Responses API path (GPT models via Copilot)
101
+ let nativeInput = null;
102
+ let nativeInstructions = "";
103
+ return {
104
+ id: "github-copilot",
105
+ model: config.model,
106
+ client,
107
+ capabilities,
108
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
109
+ /* v8 ignore start -- responses path: tested via mock assertions in github-copilot.test.ts @preserve */
110
+ resetTurnState(messages) {
111
+ const { instructions, input } = (0, streaming_1.toResponsesInput)(messages);
112
+ nativeInput = input;
113
+ nativeInstructions = instructions;
114
+ },
115
+ appendToolOutput(callId, output) {
116
+ if (!nativeInput)
117
+ return;
118
+ nativeInput.push({ type: "function_call_output", call_id: callId, output });
119
+ },
120
+ async streamTurn(request) {
121
+ if (!nativeInput)
122
+ this.resetTurnState(request.messages);
123
+ const params = {
124
+ model: this.model,
125
+ input: nativeInput,
126
+ instructions: nativeInstructions,
127
+ tools: (0, streaming_1.toResponsesTools)(request.activeTools),
128
+ reasoning: { effort: request.reasoningEffort ?? "medium", summary: "detailed" },
129
+ stream: true,
130
+ store: false,
131
+ include: ["reasoning.encrypted_content"],
132
+ };
133
+ if (request.traceId)
134
+ params.metadata = { trace_id: request.traceId };
135
+ if (request.toolChoiceRequired)
136
+ params.tool_choice = "required";
137
+ try {
138
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
139
+ for (const item of result.outputItems)
140
+ nativeInput.push(item);
141
+ return result;
142
+ }
143
+ catch (error) {
144
+ throw withAuthGuidance(error);
145
+ }
146
+ },
147
+ /* v8 ignore stop */
148
+ };
149
+ }
@@ -8,6 +8,7 @@ const openai_1 = __importDefault(require("openai"));
8
8
  const config_1 = require("../config");
9
9
  const runtime_1 = require("../../nerves/runtime");
10
10
  const streaming_1 = require("../streaming");
11
+ const model_capabilities_1 = require("../model-capabilities");
11
12
  function createMinimaxProviderRuntime() {
12
13
  (0, runtime_1.emitNervesEvent)({
13
14
  component: "engine",
@@ -19,6 +20,8 @@ function createMinimaxProviderRuntime() {
19
20
  if (!minimaxConfig.apiKey) {
20
21
  throw new Error("provider 'minimax' is selected in agent.json but providers.minimax.apiKey is missing in secrets.json.");
21
22
  }
23
+ // Registry consulted; MiniMax models return empty defaults (no capabilities to derive)
24
+ (0, model_capabilities_1.getModelCapabilities)(minimaxConfig.model);
22
25
  const client = new openai_1.default({
23
26
  apiKey: minimaxConfig.apiKey,
24
27
  baseURL: "https://api.minimaxi.chat/v1",
@@ -29,6 +32,7 @@ function createMinimaxProviderRuntime() {
29
32
  id: "minimax",
30
33
  model: minimaxConfig.model,
31
34
  client,
35
+ capabilities: new Set(),
32
36
  resetTurnState(_messages) {
33
37
  // No provider-owned turn state for chat-completions providers.
34
38
  },
@@ -9,6 +9,7 @@ const config_1 = require("../config");
9
9
  const identity_1 = require("../identity");
10
10
  const runtime_1 = require("../../nerves/runtime");
11
11
  const streaming_1 = require("../streaming");
12
+ const model_capabilities_1 = require("../model-capabilities");
12
13
  const OPENAI_CODEX_AUTH_FAILURE_MARKERS = [
13
14
  "authentication failed",
14
15
  "unauthorized",
@@ -27,11 +28,11 @@ function getOpenAICodexOAuthInstructions() {
27
28
  const agentName = getOpenAICodexAgentNameForGuidance();
28
29
  return [
29
30
  "Fix:",
30
- ` 1. Run \`npm run auth:openai-codex -- --agent ${agentName}\``,
31
- " (or run `codex login` and set the OAuth token manually)",
31
+ ` 1. Run \`ouro auth --agent ${agentName}\``,
32
32
  ` 2. Open ${getOpenAICodexSecretsPathForGuidance()}`,
33
33
  " 3. Confirm providers.openai-codex.oauthAccessToken is set",
34
34
  " 4. This provider uses chatgpt.com/backend-api/codex/responses (not api.openai.com/responses).",
35
+ " 5. After reauth, retry the failed ouro command or reconnect this session.",
35
36
  ].join("\n");
36
37
  }
37
38
  function getOpenAICodexReauthGuidance(reason) {
@@ -106,6 +107,12 @@ function createOpenAICodexProviderRuntime() {
106
107
  if (!chatgptAccountId) {
107
108
  throw new Error(getOpenAICodexReauthGuidance("OpenAI Codex OAuth access token is missing a chatgpt_account_id claim required for chatgpt.com/backend-api/codex."));
108
109
  }
110
+ const modelCaps = (0, model_capabilities_1.getModelCapabilities)(codexConfig.model);
111
+ const capabilities = new Set();
112
+ if (modelCaps.reasoningEffort)
113
+ capabilities.add("reasoning-effort");
114
+ if (modelCaps.phase)
115
+ capabilities.add("phase-annotation");
109
116
  const client = new openai_1.default({
110
117
  apiKey: token,
111
118
  baseURL: OPENAI_CODEX_BACKEND_BASE_URL,
@@ -123,6 +130,8 @@ function createOpenAICodexProviderRuntime() {
123
130
  id: "openai-codex",
124
131
  model: codexConfig.model,
125
132
  client,
133
+ capabilities,
134
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
126
135
  resetTurnState(messages) {
127
136
  const { instructions, input } = (0, streaming_1.toResponsesInput)(messages);
128
137
  nativeInput = input;
@@ -141,7 +150,7 @@ function createOpenAICodexProviderRuntime() {
141
150
  input: nativeInput,
142
151
  instructions: nativeInstructions,
143
152
  tools: (0, streaming_1.toResponsesTools)(request.activeTools),
144
- reasoning: { effort: "medium", summary: "detailed" },
153
+ reasoning: { effort: request.reasoningEffort ?? "medium", summary: "detailed" },
145
154
  stream: true,
146
155
  store: false,
147
156
  include: ["reasoning.encrypted_content"],
@@ -0,0 +1,362 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resetSafeWorkspaceSelection = resetSafeWorkspaceSelection;
37
+ exports.getActiveSafeWorkspaceSelection = getActiveSafeWorkspaceSelection;
38
+ exports.ensureSafeRepoWorkspace = ensureSafeRepoWorkspace;
39
+ exports.resolveSafeRepoPath = resolveSafeRepoPath;
40
+ exports.resolveSafeShellExecution = resolveSafeShellExecution;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const child_process_1 = require("child_process");
44
+ const identity_1 = require("./identity");
45
+ const runtime_1 = require("../nerves/runtime");
46
+ let activeSelection = null;
47
+ let cleanupHookRegistered = false;
48
+ function workspaceSelectionStateFile(workspaceBase) {
49
+ return path.join(workspaceBase, ".active-safe-workspace.json");
50
+ }
51
+ function getOptionalFsFn(name) {
52
+ try {
53
+ return fs[name];
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ function shouldPersistSelection(options) {
60
+ return options.persistSelection ?? options.workspaceRoot === undefined;
61
+ }
62
+ function isPersistedSelectionShape(value) {
63
+ if (!value || typeof value !== "object")
64
+ return false;
65
+ const candidate = value;
66
+ return (typeof candidate.runtimeKind === "string"
67
+ && typeof candidate.repoRoot === "string"
68
+ && typeof candidate.workspaceRoot === "string"
69
+ && typeof candidate.workspaceBranch === "string"
70
+ && (candidate.sourceBranch === null || typeof candidate.sourceBranch === "string")
71
+ && typeof candidate.sourceCloneUrl === "string"
72
+ && typeof candidate.cleanupAfterMerge === "boolean"
73
+ && typeof candidate.created === "boolean"
74
+ && typeof candidate.note === "string");
75
+ }
76
+ function loadPersistedSelection(workspaceBase, options) {
77
+ const existsSync = options.existsSync ?? fs.existsSync;
78
+ const readFileSync = options.readFileSync ?? getOptionalFsFn("readFileSync");
79
+ const unlinkSync = options.unlinkSync ?? getOptionalFsFn("unlinkSync");
80
+ const stateFile = workspaceSelectionStateFile(workspaceBase);
81
+ if (!existsSync(stateFile))
82
+ return null;
83
+ if (!readFileSync)
84
+ return null;
85
+ try {
86
+ const raw = readFileSync(stateFile, "utf-8");
87
+ const parsed = JSON.parse(raw);
88
+ if (!isPersistedSelectionShape(parsed) || !existsSync(parsed.workspaceRoot)) {
89
+ try {
90
+ unlinkSync?.(stateFile);
91
+ }
92
+ catch {
93
+ // best effort
94
+ }
95
+ return null;
96
+ }
97
+ return parsed;
98
+ }
99
+ catch {
100
+ try {
101
+ unlinkSync?.(stateFile);
102
+ }
103
+ catch {
104
+ // best effort
105
+ }
106
+ return null;
107
+ }
108
+ }
109
+ function persistSelectionState(workspaceBase, selection, options) {
110
+ const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
111
+ const writeFileSync = options.writeFileSync ?? getOptionalFsFn("writeFileSync");
112
+ if (!writeFileSync)
113
+ return;
114
+ mkdirSync(workspaceBase, { recursive: true });
115
+ writeFileSync(workspaceSelectionStateFile(workspaceBase), JSON.stringify(selection, null, 2), "utf-8");
116
+ }
117
+ function defaultNow() {
118
+ return Date.now();
119
+ }
120
+ function resolveAgentName(explicit) {
121
+ if (explicit && explicit.trim().length > 0)
122
+ return explicit.trim();
123
+ try {
124
+ return (0, identity_1.getAgentName)();
125
+ }
126
+ catch {
127
+ return "slugger";
128
+ }
129
+ }
130
+ function runGit(cwd, args, spawnSync) {
131
+ return spawnSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
132
+ }
133
+ function readStdout(result) {
134
+ return (result.stdout ?? Buffer.from("")).toString("utf-8").trim();
135
+ }
136
+ function readStderr(result) {
137
+ return (result.stderr ?? Buffer.from("")).toString("utf-8").trim();
138
+ }
139
+ function assertGitOk(result, action) {
140
+ if (result.error) {
141
+ throw result.error;
142
+ }
143
+ if (result.status !== 0) {
144
+ const detail = readStderr(result) || readStdout(result) || `exit ${result.status ?? "unknown"}`;
145
+ throw new Error(`${action} failed: ${detail}`);
146
+ }
147
+ return readStdout(result);
148
+ }
149
+ function isGitClone(repoRoot, spawnSync) {
150
+ const result = runGit(repoRoot, ["rev-parse", "--is-inside-work-tree"], spawnSync);
151
+ return result.status === 0 && readStdout(result) === "true";
152
+ }
153
+ function readCurrentBranch(repoRoot, spawnSync) {
154
+ return assertGitOk(runGit(repoRoot, ["rev-parse", "--abbrev-ref", "HEAD"], spawnSync), "git branch read");
155
+ }
156
+ function ensureFetchedOrigin(repoRoot, spawnSync) {
157
+ assertGitOk(runGit(repoRoot, ["fetch", "origin"], spawnSync), "git fetch origin");
158
+ }
159
+ function ensureMainFastForward(repoRoot, spawnSync) {
160
+ assertGitOk(runGit(repoRoot, ["pull", "--ff-only", "origin", "main"], spawnSync), "git pull --ff-only origin main");
161
+ }
162
+ function createDedicatedWorktree(repoRoot, workspaceRoot, branchSuffix, existsSync, mkdirSync, rmSync, spawnSync) {
163
+ mkdirSync(path.dirname(workspaceRoot), { recursive: true });
164
+ const branchName = `slugger/${branchSuffix}`;
165
+ if (existsSync(workspaceRoot)) {
166
+ rmSync(workspaceRoot, { recursive: true, force: true });
167
+ }
168
+ assertGitOk(runGit(repoRoot, ["worktree", "add", "-B", branchName, workspaceRoot, "origin/main"], spawnSync), "git worktree add");
169
+ return { workspaceRoot, created: true, branchName };
170
+ }
171
+ function createScratchClone(workspaceRoot, cloneUrl, existsSync, mkdirSync, rmSync, spawnSync) {
172
+ mkdirSync(path.dirname(workspaceRoot), { recursive: true });
173
+ if (existsSync(workspaceRoot)) {
174
+ rmSync(workspaceRoot, { recursive: true, force: true });
175
+ }
176
+ const result = spawnSync("git", ["clone", "--depth", "1", "--branch", "main", cloneUrl, workspaceRoot], {
177
+ stdio: ["ignore", "pipe", "pipe"],
178
+ });
179
+ assertGitOk(result, "git clone");
180
+ return { workspaceRoot, created: true, branchName: "main" };
181
+ }
182
+ const REPO_LOCAL_SHELL_COMMAND = /^(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)*(git|npm|npx|node|pnpm|yarn|bun|rg|sed|cat|ls|find|grep|vitest|tsc|eslint)\b/;
183
+ function looksRepoLocalShellCommand(command) {
184
+ return REPO_LOCAL_SHELL_COMMAND.test(command.trim());
185
+ }
186
+ function registerCleanupHook(options) {
187
+ if (cleanupHookRegistered)
188
+ return;
189
+ cleanupHookRegistered = true;
190
+ process.on("exit", () => {
191
+ if (!activeSelection?.cleanupAfterMerge)
192
+ return;
193
+ try {
194
+ options.rmSync(activeSelection.workspaceRoot, { recursive: true, force: true });
195
+ }
196
+ catch {
197
+ // best effort
198
+ }
199
+ });
200
+ }
201
+ function resetSafeWorkspaceSelection(options = {}) {
202
+ activeSelection = null;
203
+ if (!options.keepCleanupHookRegistered) {
204
+ cleanupHookRegistered = false;
205
+ }
206
+ }
207
+ function getActiveSafeWorkspaceSelection() {
208
+ return activeSelection;
209
+ }
210
+ function ensureSafeRepoWorkspace(options = {}) {
211
+ if (activeSelection) {
212
+ return activeSelection;
213
+ }
214
+ const repoRoot = options.repoRoot ?? (0, identity_1.getRepoRoot)();
215
+ const agentName = resolveAgentName(options.agentName);
216
+ const canonicalRepoUrl = options.canonicalRepoUrl ?? identity_1.HARNESS_CANONICAL_REPO_URL;
217
+ const workspaceBase = options.workspaceRoot ?? (0, identity_1.getAgentRepoWorkspacesRoot)(agentName);
218
+ const persistSelection = shouldPersistSelection(options);
219
+ const spawnSync = options.spawnSync ?? child_process_1.spawnSync;
220
+ const existsSync = options.existsSync ?? fs.existsSync;
221
+ const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
222
+ const rmSync = options.rmSync ?? fs.rmSync;
223
+ const now = options.now ?? defaultNow;
224
+ const stamp = String(now());
225
+ registerCleanupHook({ rmSync });
226
+ if (persistSelection) {
227
+ const restored = loadPersistedSelection(workspaceBase, options);
228
+ if (restored) {
229
+ activeSelection = restored;
230
+ (0, runtime_1.emitNervesEvent)({
231
+ component: "workspace",
232
+ event: "workspace.safe_repo_restored",
233
+ message: "restored safe repo workspace after runtime restart",
234
+ meta: {
235
+ runtimeKind: restored.runtimeKind,
236
+ repoRoot: restored.repoRoot,
237
+ workspaceRoot: restored.workspaceRoot,
238
+ workspaceBranch: restored.workspaceBranch,
239
+ sourceBranch: restored.sourceBranch,
240
+ cleanupAfterMerge: restored.cleanupAfterMerge,
241
+ },
242
+ });
243
+ return restored;
244
+ }
245
+ }
246
+ let selection;
247
+ if (isGitClone(repoRoot, spawnSync)) {
248
+ const branch = readCurrentBranch(repoRoot, spawnSync);
249
+ ensureFetchedOrigin(repoRoot, spawnSync);
250
+ if (branch === "main") {
251
+ ensureMainFastForward(repoRoot, spawnSync);
252
+ const worktreeRoot = path.join(workspaceBase, `ouroboros-main-${stamp}`);
253
+ const created = createDedicatedWorktree(repoRoot, worktreeRoot, `safe-workspace-${stamp}`, existsSync, mkdirSync, rmSync, spawnSync);
254
+ selection = {
255
+ runtimeKind: "clone-main",
256
+ repoRoot,
257
+ workspaceRoot: created.workspaceRoot,
258
+ workspaceBranch: created.branchName,
259
+ sourceBranch: branch,
260
+ sourceCloneUrl: canonicalRepoUrl,
261
+ cleanupAfterMerge: false,
262
+ created: created.created,
263
+ note: `running from clone on main; fast-forwarded and created dedicated worktree ${created.workspaceRoot}`,
264
+ };
265
+ }
266
+ else {
267
+ const worktreeRoot = path.join(workspaceBase, `ouroboros-origin-main-${stamp}`);
268
+ const created = createDedicatedWorktree(repoRoot, worktreeRoot, `safe-workspace-${stamp}`, existsSync, mkdirSync, rmSync, spawnSync);
269
+ selection = {
270
+ runtimeKind: "clone-non-main",
271
+ repoRoot,
272
+ workspaceRoot: created.workspaceRoot,
273
+ workspaceBranch: created.branchName,
274
+ sourceBranch: branch,
275
+ sourceCloneUrl: canonicalRepoUrl,
276
+ cleanupAfterMerge: false,
277
+ created: created.created,
278
+ note: `running from branch ${branch}; defaulted new work from origin/main in dedicated worktree ${created.workspaceRoot}`,
279
+ };
280
+ }
281
+ }
282
+ else {
283
+ const scratchRoot = path.join(workspaceBase, `ouroboros-scratch-${stamp}`);
284
+ const created = createScratchClone(scratchRoot, canonicalRepoUrl, existsSync, mkdirSync, rmSync, spawnSync);
285
+ selection = {
286
+ runtimeKind: "installed-runtime",
287
+ repoRoot,
288
+ workspaceRoot: created.workspaceRoot,
289
+ workspaceBranch: created.branchName,
290
+ sourceBranch: null,
291
+ sourceCloneUrl: canonicalRepoUrl,
292
+ cleanupAfterMerge: true,
293
+ created: created.created,
294
+ note: `running from installed runtime/wrapper; created scratch clone ${created.workspaceRoot} from ${canonicalRepoUrl}`,
295
+ };
296
+ }
297
+ activeSelection = selection;
298
+ if (persistSelection) {
299
+ persistSelectionState(workspaceBase, selection, options);
300
+ }
301
+ (0, runtime_1.emitNervesEvent)({
302
+ component: "workspace",
303
+ event: "workspace.safe_repo_acquired",
304
+ message: "acquired safe repo workspace before local edits",
305
+ meta: {
306
+ runtimeKind: selection.runtimeKind,
307
+ repoRoot: selection.repoRoot,
308
+ workspaceRoot: selection.workspaceRoot,
309
+ workspaceBranch: selection.workspaceBranch,
310
+ sourceBranch: selection.sourceBranch,
311
+ sourceCloneUrl: selection.sourceCloneUrl,
312
+ cleanupAfterMerge: selection.cleanupAfterMerge,
313
+ },
314
+ });
315
+ return selection;
316
+ }
317
+ function resolveSafeRepoPath(options) {
318
+ const rawRequestedPath = options.requestedPath;
319
+ const repoRoot = path.resolve(options.repoRoot ?? (0, identity_1.getRepoRoot)());
320
+ if (!path.isAbsolute(rawRequestedPath) && !rawRequestedPath.startsWith("~")) {
321
+ const selection = activeSelection ?? ensureSafeRepoWorkspace(options);
322
+ return {
323
+ selection,
324
+ resolvedPath: path.resolve(selection.workspaceRoot, rawRequestedPath),
325
+ };
326
+ }
327
+ const requestedPath = path.resolve(rawRequestedPath);
328
+ if (activeSelection && requestedPath.startsWith(activeSelection.workspaceRoot + path.sep)) {
329
+ return { selection: activeSelection, resolvedPath: requestedPath };
330
+ }
331
+ if (requestedPath !== repoRoot && !requestedPath.startsWith(repoRoot + path.sep)) {
332
+ return { selection: activeSelection, resolvedPath: requestedPath };
333
+ }
334
+ const selection = ensureSafeRepoWorkspace(options);
335
+ const relativePath = requestedPath === repoRoot ? "" : path.relative(repoRoot, requestedPath);
336
+ const resolvedPath = relativePath ? path.join(selection.workspaceRoot, relativePath) : selection.workspaceRoot;
337
+ return { selection, resolvedPath };
338
+ }
339
+ function resolveSafeShellExecution(command, options = {}) {
340
+ const trimmed = command.trim();
341
+ if (!trimmed) {
342
+ return { selection: activeSelection, command };
343
+ }
344
+ if (activeSelection && command.includes(activeSelection.workspaceRoot)) {
345
+ return { selection: activeSelection, command, cwd: activeSelection.workspaceRoot };
346
+ }
347
+ const repoRoot = path.resolve(options.repoRoot ?? (0, identity_1.getRepoRoot)());
348
+ const mentionsRepoRoot = command.includes(repoRoot);
349
+ const shouldRoute = mentionsRepoRoot || looksRepoLocalShellCommand(trimmed);
350
+ if (!shouldRoute) {
351
+ return { selection: activeSelection, command };
352
+ }
353
+ const selection = ensureSafeRepoWorkspace(options);
354
+ const rewrittenCommand = mentionsRepoRoot
355
+ ? command.split(repoRoot).join(selection.workspaceRoot)
356
+ : command;
357
+ return {
358
+ selection,
359
+ command: rewrittenCommand,
360
+ cwd: selection.workspaceRoot,
361
+ };
362
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSenseInventory = getSenseInventory;
4
+ const runtime_1 = require("../nerves/runtime");
5
+ const identity_1 = require("./identity");
6
+ const SENSES = [
7
+ { sense: "cli", label: "CLI", daemonManaged: false },
8
+ { sense: "teams", label: "Teams", daemonManaged: true },
9
+ { sense: "bluebubbles", label: "BlueBubbles", daemonManaged: true },
10
+ ];
11
+ function configuredSenses(senses) {
12
+ return senses ?? {
13
+ cli: { ...identity_1.DEFAULT_AGENT_SENSES.cli },
14
+ teams: { ...identity_1.DEFAULT_AGENT_SENSES.teams },
15
+ bluebubbles: { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
16
+ };
17
+ }
18
+ function resolveStatus(enabled, daemonManaged, runtimeInfo) {
19
+ if (!enabled) {
20
+ return "disabled";
21
+ }
22
+ if (!daemonManaged) {
23
+ return "interactive";
24
+ }
25
+ if (runtimeInfo?.runtime === "error") {
26
+ return "error";
27
+ }
28
+ if (runtimeInfo?.runtime === "running") {
29
+ return "running";
30
+ }
31
+ if (runtimeInfo?.configured === false) {
32
+ return "needs_config";
33
+ }
34
+ return "ready";
35
+ }
36
+ function getSenseInventory(agent, runtime = {}) {
37
+ const senses = configuredSenses(agent.senses);
38
+ const inventory = SENSES.map(({ sense, label, daemonManaged }) => {
39
+ const enabled = senses[sense].enabled;
40
+ return {
41
+ sense,
42
+ label,
43
+ enabled,
44
+ daemonManaged,
45
+ status: resolveStatus(enabled, daemonManaged, runtime[sense]),
46
+ };
47
+ });
48
+ (0, runtime_1.emitNervesEvent)({
49
+ component: "channels",
50
+ event: "channel.sense_inventory_built",
51
+ message: "built sense inventory",
52
+ meta: {
53
+ senses: inventory.map((entry) => ({
54
+ sense: entry.sense,
55
+ enabled: entry.enabled,
56
+ status: entry.status,
57
+ })),
58
+ },
59
+ });
60
+ return inventory;
61
+ }