@ouro.bot/cli 0.1.0-alpha.201 → 0.1.0-alpha.203

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,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.203",
6
+ "changes": [
7
+ "refactor: unify obligation systems — mind/obligations.ts merged into heart/obligations.ts with prefixed ReturnObligation API"
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.202",
12
+ "changes": [
13
+ "refactor: unify hatch + runtime auth into single credential path (PROVIDER_CREDENTIALS table)",
14
+ "fix: github-copilot credential discovery from stored secrets and env vars",
15
+ "feat: ping-verify credentials before entering serpent guide session",
16
+ "fix: clean up React tooltip timeouts on unmount to prevent state update warning",
17
+ "fix: guard against undefined child from spawn to prevent unhandled rejections"
18
+ ]
19
+ },
4
20
  {
5
21
  "version": "0.1.0-alpha.201",
6
22
  "changes": [
@@ -41,6 +41,7 @@ exports.getMinimaxConfig = getMinimaxConfig;
41
41
  exports.getAnthropicConfig = getAnthropicConfig;
42
42
  exports.getOpenAICodexConfig = getOpenAICodexConfig;
43
43
  exports.getGithubCopilotConfig = getGithubCopilotConfig;
44
+ exports.getProviderConfig = getProviderConfig;
44
45
  exports.getTeamsConfig = getTeamsConfig;
45
46
  exports.getTeamsSecondaryConfig = getTeamsSecondaryConfig;
46
47
  exports.getContextConfig = getContextConfig;
@@ -270,6 +271,10 @@ function getGithubCopilotConfig() {
270
271
  const config = loadConfig();
271
272
  return { ...config.providers["github-copilot"] };
272
273
  }
274
+ function getProviderConfig(provider) {
275
+ const config = loadConfig();
276
+ return { ...config.providers[provider] };
277
+ }
273
278
  function getTeamsConfig() {
274
279
  const config = loadConfig();
275
280
  return { ...config.teams };
@@ -42,31 +42,8 @@ function getProviderRuntimeFingerprint(facing) {
42
42
  const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
43
43
  const provider = facingConfig.provider;
44
44
  const model = facingConfig.model;
45
- /* v8 ignore next -- switch: not all provider branches exercised in CI @preserve */
46
- switch (provider) {
47
- case "azure": {
48
- const { apiKey, endpoint, deployment, apiVersion, managedIdentityClientId } = (0, config_1.getAzureConfig)();
49
- return JSON.stringify({ provider, model, apiKey, endpoint, deployment, apiVersion, managedIdentityClientId });
50
- }
51
- case "anthropic": {
52
- const { setupToken } = (0, config_1.getAnthropicConfig)();
53
- return JSON.stringify({ provider, model, setupToken });
54
- }
55
- case "minimax": {
56
- const { apiKey } = (0, config_1.getMinimaxConfig)();
57
- return JSON.stringify({ provider, model, apiKey });
58
- }
59
- case "openai-codex": {
60
- const { oauthAccessToken } = (0, config_1.getOpenAICodexConfig)();
61
- return JSON.stringify({ provider, model, oauthAccessToken });
62
- }
63
- /* v8 ignore start -- fingerprint: tested via provider init tests @preserve */
64
- case "github-copilot": {
65
- const { githubToken, baseUrl } = (0, config_1.getGithubCopilotConfig)();
66
- return JSON.stringify({ provider, model, githubToken, baseUrl });
67
- }
68
- /* v8 ignore stop */
69
- }
45
+ const providerConfig = (0, config_1.getProviderConfig)(provider);
46
+ return JSON.stringify({ provider, model, ...providerConfig });
70
47
  }
71
48
  function createProviderRegistry() {
72
49
  const factories = {
@@ -87,8 +87,8 @@ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
87
87
  fix: `Add humanFacing.provider to ${agentJsonPath}. Valid providers: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
88
88
  };
89
89
  }
90
- const requiredFields = identity_1.PROVIDER_CREDENTIALS[provider];
91
- if (!requiredFields) {
90
+ const desc = identity_1.PROVIDER_CREDENTIALS[provider];
91
+ if (!desc) {
92
92
  return {
93
93
  ok: false,
94
94
  error: `Unknown provider '${provider}' in agent.json for '${agentName}'`,
@@ -128,7 +128,7 @@ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
128
128
  return { ok: true };
129
129
  }
130
130
  }
131
- const missing = requiredFields.filter((field) => {
131
+ const missing = desc.required.filter((field) => {
132
132
  const val = providerSecrets[field];
133
133
  return typeof val !== "string" || val.length === 0;
134
134
  });
@@ -263,7 +263,7 @@ async function collectRuntimeAuthCredentials(input, deps) {
263
263
  const spawnSync = deps.spawnSync ?? child_process_1.spawnSync;
264
264
  const homeDir = deps.homeDir ?? os.homedir();
265
265
  if (input.provider === "github-copilot") {
266
- let token = process.env.GH_TOKEN?.trim() || "";
266
+ let token = process.env.GH_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim() || "";
267
267
  if (!token) {
268
268
  const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
269
269
  token = (result.status === 0 && result.stdout ? result.stdout.trim() : "");
@@ -361,95 +361,62 @@ async function collectRuntimeAuthCredentials(input, deps) {
361
361
  /* v8 ignore stop */
362
362
  return { setupToken };
363
363
  }
364
- if (input.provider === "minimax") {
365
- const prompt = ensurePromptInput(input.promptInput, input.provider);
366
- const apiKey = (await prompt("MiniMax API key: ")).trim();
367
- if (!apiKey)
368
- throw new Error("MiniMax API key is required.");
369
- return { apiKey };
370
- }
364
+ // Generic prompt-for-fields fallback (minimax, azure, any future simple providers)
371
365
  const prompt = ensurePromptInput(input.promptInput, input.provider);
372
- const apiKey = (await prompt("Azure API key: ")).trim();
373
- const endpoint = (await prompt("Azure endpoint: ")).trim();
374
- const deployment = (await prompt("Azure deployment: ")).trim();
375
- if (!apiKey || !endpoint || !deployment) {
376
- throw new Error("Azure API key, endpoint, and deployment are required.");
366
+ const desc = identity_1.PROVIDER_CREDENTIALS[input.provider];
367
+ const creds = {};
368
+ for (const field of desc.required) {
369
+ /* v8 ignore next -- fallback: all current providers define promptLabels for required fields @preserve */
370
+ const label = desc.promptLabels[field] ?? field;
371
+ const value = (await prompt(`${label}: `)).trim();
372
+ if (!value)
373
+ throw new Error(`${label} is required.`);
374
+ creds[field] = value;
377
375
  }
378
- return { apiKey, endpoint, deployment };
376
+ return creds;
379
377
  }
380
378
  async function resolveHatchCredentials(input) {
381
- const prompt = input.promptInput;
382
379
  const credentials = { ...(input.credentials ?? {}) };
383
- if (input.provider === "github-copilot" && !credentials.githubToken && input.runAuthFlow) {
384
- const result = await input.runAuthFlow({
385
- agentName: input.agentName,
386
- provider: "github-copilot",
387
- promptInput: prompt,
388
- });
389
- Object.assign(credentials, result.credentials);
390
- }
391
- if (input.provider === "anthropic" && !credentials.setupToken && input.runAuthFlow) {
380
+ // If all required fields are already provided, return as-is
381
+ const cred = credentials;
382
+ const missing = identity_1.PROVIDER_CREDENTIALS[input.provider].required.some((key) => !cred[key]);
383
+ if (!missing)
384
+ return credentials;
385
+ // Try the full auth flow (wraps collectRuntimeAuthCredentials + writes secrets)
386
+ if (input.runAuthFlow) {
392
387
  const result = await input.runAuthFlow({
393
388
  agentName: input.agentName,
394
- provider: "anthropic",
395
- promptInput: prompt,
389
+ provider: input.provider,
390
+ promptInput: input.promptInput,
396
391
  });
397
392
  Object.assign(credentials, result.credentials);
393
+ /* v8 ignore next 3 -- branch: auth flow always fills all required fields in production @preserve */
394
+ if (!identity_1.PROVIDER_CREDENTIALS[input.provider].required.some((key) => !credentials[key])) {
395
+ return credentials;
396
+ }
398
397
  }
399
- if (input.provider === "anthropic" && !credentials.setupToken && prompt) {
400
- credentials.setupToken = await prompt("Anthropic setup-token: ");
401
- }
402
- if (input.provider === "openai-codex" && !credentials.oauthAccessToken && input.runAuthFlow) {
403
- const result = await input.runAuthFlow({
404
- agentName: input.agentName,
405
- provider: "openai-codex",
406
- promptInput: prompt,
407
- });
408
- Object.assign(credentials, result.credentials);
409
- }
410
- if (input.provider === "openai-codex" && !credentials.oauthAccessToken && prompt) {
411
- credentials.oauthAccessToken = await prompt("OpenAI Codex OAuth token: ");
412
- }
413
- if (input.provider === "minimax" && !credentials.apiKey && prompt) {
414
- credentials.apiKey = await prompt("MiniMax API key: ");
415
- }
416
- if (input.provider === "azure") {
417
- if (!credentials.apiKey && prompt)
418
- credentials.apiKey = await prompt("Azure API key: ");
419
- if (!credentials.endpoint && prompt)
420
- credentials.endpoint = await prompt("Azure endpoint: ");
421
- if (!credentials.deployment && prompt)
422
- credentials.deployment = await prompt("Azure deployment: ");
398
+ // Prompt for any still-missing required fields
399
+ /* v8 ignore next -- guard: no promptInput means we can't collect remaining fields @preserve */
400
+ if (input.promptInput) {
401
+ const desc = identity_1.PROVIDER_CREDENTIALS[input.provider];
402
+ for (const field of desc.required) {
403
+ if (!cred[field]) {
404
+ const label = desc.promptLabels[field] ?? field;
405
+ cred[field] = await input.promptInput(`${label}: `);
406
+ }
407
+ }
423
408
  }
424
409
  return credentials;
425
410
  }
426
411
  function applyCredentials(secrets, provider, credentials) {
427
- if (provider === "anthropic") {
428
- secrets.providers.anthropic.setupToken = credentials.setupToken.trim();
429
- /* v8 ignore start -- token refresh fields: populated when OAuth exchange succeeds @preserve */
430
- if (credentials.refreshToken)
431
- secrets.providers.anthropic.refreshToken = credentials.refreshToken;
432
- if (credentials.expiresAt)
433
- secrets.providers.anthropic.expiresAt = credentials.expiresAt;
434
- /* v8 ignore stop */
435
- return;
436
- }
437
- if (provider === "github-copilot") {
438
- secrets.providers["github-copilot"].githubToken = credentials.githubToken.trim();
439
- secrets.providers["github-copilot"].baseUrl = credentials.baseUrl.trim();
440
- return;
441
- }
442
- if (provider === "openai-codex") {
443
- secrets.providers["openai-codex"].oauthAccessToken = credentials.oauthAccessToken.trim();
444
- return;
445
- }
446
- if (provider === "minimax") {
447
- secrets.providers.minimax.apiKey = credentials.apiKey.trim();
448
- return;
412
+ const target = secrets.providers[provider];
413
+ // Copy all non-empty credential fields to the provider's secrets block
414
+ for (const [key, value] of Object.entries(credentials)) {
415
+ /* v8 ignore next -- guard: skip null/empty fields from partial credential objects @preserve */
416
+ if (value != null && value !== "") {
417
+ target[key] = typeof value === "string" ? value.trim() : value;
418
+ }
449
419
  }
450
- secrets.providers.azure.apiKey = credentials.apiKey.trim();
451
- secrets.providers.azure.endpoint = credentials.endpoint.trim();
452
- secrets.providers.azure.deployment = credentials.deployment.trim();
453
420
  }
454
421
  async function runRuntimeAuthFlow(input, deps = {}) {
455
422
  (0, runtime_1.emitNervesEvent)({
@@ -561,20 +561,10 @@ function parseLinkCommand(args, kind = "friend.link") {
561
561
  function isAgentProvider(value) {
562
562
  return value === "azure" || value === "anthropic" || value === "minimax" || value === "openai-codex" || value === "github-copilot";
563
563
  }
564
- /* v8 ignore start -- hasStoredCredentials: per-provider branches tested via auth switch tests @preserve */
564
+ /* v8 ignore next 3 -- only called from auth.switch inside integration block @preserve */
565
565
  function hasStoredCredentials(provider, providerSecrets) {
566
- if (provider === "anthropic")
567
- return !!providerSecrets.setupToken;
568
- if (provider === "openai-codex")
569
- return !!providerSecrets.oauthAccessToken;
570
- if (provider === "github-copilot")
571
- return !!providerSecrets.githubToken;
572
- if (provider === "minimax")
573
- return !!providerSecrets.apiKey;
574
- // azure
575
- return !!providerSecrets.endpoint && !!providerSecrets.apiKey;
566
+ return identity_1.PROVIDER_CREDENTIALS[provider].required.every((key) => !!providerSecrets[key]);
576
567
  }
577
- /* v8 ignore stop */
578
568
  /* v8 ignore start -- verifyProviderCredentials: delegates to pingProvider @preserve */
579
569
  async function verifyProviderCredentials(provider, providers) {
580
570
  const config = providers[provider];
@@ -1379,17 +1369,16 @@ function discoverExistingCredentials(secretsRoot) {
1379
1369
  if (!parsed.providers)
1380
1370
  continue;
1381
1371
  for (const [provName, provConfig] of Object.entries(parsed.providers)) {
1382
- if (provName === "anthropic" && provConfig.setupToken) {
1383
- found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken }, providerConfig: { ...provConfig } });
1384
- }
1385
- else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
1386
- found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken }, providerConfig: { ...provConfig } });
1387
- }
1388
- else if (provName === "minimax" && provConfig.apiKey) {
1389
- found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey }, providerConfig: { ...provConfig } });
1390
- }
1391
- else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
1392
- found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment }, providerConfig: { ...provConfig } });
1372
+ const desc = identity_1.PROVIDER_CREDENTIALS[provName];
1373
+ /* v8 ignore next -- guard: unknown provider names in stored secrets @preserve */
1374
+ if (!desc)
1375
+ continue;
1376
+ const hasRequired = desc.required.every((key) => !!provConfig[key]);
1377
+ if (hasRequired) {
1378
+ const credentials = {};
1379
+ for (const key of Object.keys(provConfig))
1380
+ credentials[key] = provConfig[key];
1381
+ found.push({ agentName: entry.name, provider: provName, credentials, providerConfig: { ...provConfig } });
1393
1382
  }
1394
1383
  }
1395
1384
  }
@@ -1433,33 +1422,27 @@ async function defaultRunSerpentGuide() {
1433
1422
  "github-copilot": "claude-sonnet-4.6",
1434
1423
  azure: "",
1435
1424
  };
1436
- // Scan environment variables for API keys
1437
- const envKeys = [
1438
- { provider: "anthropic", envVar: "ANTHROPIC_API_KEY", credKey: "setupToken" },
1439
- { provider: "openai-codex", envVar: "OPENAI_API_KEY", credKey: "oauthAccessToken" },
1440
- { provider: "azure", envVar: "AZURE_OPENAI_API_KEY", credKey: "apiKey" },
1441
- { provider: "azure", envVar: "AZURE_OPENAI_KEY", credKey: "apiKey" },
1442
- { provider: "minimax", envVar: "MINIMAX_API_KEY", credKey: "apiKey" },
1443
- { provider: "github-copilot", envVar: "GITHUB_TOKEN", credKey: "token" },
1444
- ];
1425
+ // Scan environment variables for API keys using the canonical descriptor
1445
1426
  const envDiscovered = [];
1446
- for (const { provider, envVar, credKey } of envKeys) {
1447
- const value = process.env[envVar];
1448
- if (value) {
1449
- const envCred = { [credKey]: value };
1450
- // For Azure, also check for endpoint and deployment env vars
1451
- if (provider === "azure") {
1452
- const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
1453
- const deployment = process.env.AZURE_OPENAI_DEPLOYMENT;
1454
- if (endpoint)
1455
- envCred.endpoint = endpoint;
1456
- if (deployment)
1457
- envCred.deployment = deployment;
1427
+ for (const [provider, desc] of Object.entries(identity_1.PROVIDER_CREDENTIALS)) {
1428
+ const envCred = {};
1429
+ let firstEnvVar;
1430
+ for (const [envVar, credKey] of Object.entries(desc.envVars)) {
1431
+ const value = process.env[envVar];
1432
+ if (value) {
1433
+ ;
1434
+ envCred[credKey] = value;
1435
+ if (!firstEnvVar)
1436
+ firstEnvVar = envVar;
1458
1437
  }
1438
+ }
1439
+ // Only register if at least one required field was found
1440
+ const hasRequired = desc.required.some((key) => !!envCred[key]);
1441
+ if (hasRequired && firstEnvVar) {
1459
1442
  const provCfg = { model: defaultModels[provider] };
1460
1443
  if (provider === "azure" && envCred.deployment)
1461
1444
  provCfg.deployment = envCred.deployment;
1462
- envDiscovered.push({ provider, agentName: "env", credentials: envCred, providerConfig: provCfg, envVar });
1445
+ envDiscovered.push({ provider, agentName: "env", credentials: envCred, providerConfig: provCfg, envVar: firstEnvVar });
1463
1446
  discovered.push({ provider, agentName: "env", credentials: envCred, providerConfig: provCfg });
1464
1447
  }
1465
1448
  }
@@ -1491,17 +1474,7 @@ async function defaultRunSerpentGuide() {
1491
1474
  }
1492
1475
  providerRaw = pRaw;
1493
1476
  providerConfig = { model: defaultModels[providerRaw] };
1494
- if (providerRaw === "anthropic")
1495
- credentials.setupToken = await coldPrompt("API key: ");
1496
- if (providerRaw === "openai-codex")
1497
- credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
1498
- if (providerRaw === "minimax")
1499
- credentials.apiKey = await coldPrompt("API key: ");
1500
- if (providerRaw === "azure") {
1501
- credentials.apiKey = await coldPrompt("API key: ");
1502
- credentials.endpoint = await coldPrompt("endpoint: ");
1503
- credentials.deployment = await coldPrompt("deployment: ");
1504
- }
1477
+ credentials = await (0, auth_flow_1.collectRuntimeAuthCredentials)({ agentName: "SerpentGuide", provider: providerRaw, promptInput: coldPrompt }, {});
1505
1478
  }
1506
1479
  }
1507
1480
  else {
@@ -1515,17 +1488,7 @@ async function defaultRunSerpentGuide() {
1515
1488
  }
1516
1489
  providerRaw = pRaw;
1517
1490
  providerConfig = { model: defaultModels[providerRaw] };
1518
- if (providerRaw === "anthropic")
1519
- credentials.setupToken = await coldPrompt("API key: ");
1520
- if (providerRaw === "openai-codex")
1521
- credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
1522
- if (providerRaw === "minimax")
1523
- credentials.apiKey = await coldPrompt("API key: ");
1524
- if (providerRaw === "azure") {
1525
- credentials.apiKey = await coldPrompt("API key: ");
1526
- credentials.endpoint = await coldPrompt("endpoint: ");
1527
- credentials.deployment = await coldPrompt("deployment: ");
1528
- }
1491
+ credentials = await (0, auth_flow_1.collectRuntimeAuthCredentials)({ agentName: "SerpentGuide", provider: providerRaw, promptInput: coldPrompt }, {});
1529
1492
  }
1530
1493
  coldRl.close();
1531
1494
  process.stdout.write("\n");
@@ -1561,6 +1524,13 @@ async function defaultRunSerpentGuide() {
1561
1524
  [providerRaw]: { ...providerConfig, ...credentials },
1562
1525
  },
1563
1526
  });
1527
+ // Ping-verify credentials before entering the serpent guide session
1528
+ const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
1529
+ const pingResult = await pingProvider(providerRaw, { ...providerConfig, ...credentials });
1530
+ if (!pingResult.ok) {
1531
+ process.stdout.write(`credentials didn't work (${pingResult.message}). run 'ouro hatch' to try again.\n`);
1532
+ return null;
1533
+ }
1564
1534
  const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
1565
1535
  const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
1566
1536
  tempDir,
@@ -3146,9 +3116,9 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3146
3116
  if (command.kind === "attention.list" || command.kind === "attention.show" || command.kind === "attention.history") {
3147
3117
  try {
3148
3118
  const agentName = command.agent ?? (0, identity_1.getAgentName)();
3149
- const { listActiveObligations, readObligation } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
3119
+ const { listActiveReturnObligations, readReturnObligation } = await Promise.resolve().then(() => __importStar(require("../obligations")));
3150
3120
  if (command.kind === "attention.list") {
3151
- const obligations = listActiveObligations(agentName);
3121
+ const obligations = listActiveReturnObligations(agentName);
3152
3122
  if (obligations.length === 0) {
3153
3123
  const message = "nothing held — attention queue is empty";
3154
3124
  deps.writeStdout(message);
@@ -3160,7 +3130,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3160
3130
  return message;
3161
3131
  }
3162
3132
  if (command.kind === "attention.show") {
3163
- const obligation = readObligation(agentName, command.id);
3133
+ const obligation = readReturnObligation(agentName, command.id);
3164
3134
  if (!obligation) {
3165
3135
  const message = `no obligation found with id ${command.id}`;
3166
3136
  deps.writeStdout(message);
@@ -3171,8 +3141,8 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3171
3141
  return message;
3172
3142
  }
3173
3143
  // attention.history: show returned obligations
3174
- const { getObligationsDir } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
3175
- const obligationsDir = getObligationsDir(agentName);
3144
+ const { getReturnObligationsDir } = await Promise.resolve().then(() => __importStar(require("../obligations")));
3145
+ const obligationsDir = getReturnObligationsDir(agentName);
3176
3146
  let entries = [];
3177
3147
  try {
3178
3148
  entries = fs.readdirSync(obligationsDir);
@@ -3219,7 +3189,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3219
3189
  const { sessionPath: getSessionPath } = await Promise.resolve().then(() => __importStar(require("../config")));
3220
3190
  const { parseCadenceToMs: parseCadenceMs, DEFAULT_CADENCE_MS } = await Promise.resolve().then(() => __importStar(require("./cadence")));
3221
3191
  const { parseFrontmatter } = await Promise.resolve().then(() => __importStar(require("../../repertoire/tasks/parser")));
3222
- const { listActiveObligations } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
3192
+ const { listActiveReturnObligations } = await Promise.resolve().then(() => __importStar(require("../obligations")));
3223
3193
  // Read runtime state
3224
3194
  const innerSessionPath = getSessionPath("inner-dialog", "inner", "session");
3225
3195
  const runtimeJsonPath = path.join(path.dirname(innerSessionPath), "runtime.json");
@@ -3272,7 +3242,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3272
3242
  }
3273
3243
  catch { /* no habits — heartbeat unknown */ }
3274
3244
  // Attention count
3275
- const activeObligations = listActiveObligations(agentName);
3245
+ const activeObligations = listActiveReturnObligations(agentName);
3276
3246
  const message = buildInnerStatusOutput({
3277
3247
  agentName,
3278
3248
  runtimeState,
@@ -45,16 +45,7 @@ const auth_flow_1 = require("./auth-flow");
45
45
  const habit_parser_1 = require("./habit-parser");
46
46
  const hatch_specialist_1 = require("./hatch-specialist");
47
47
  function requiredCredentialKeys(provider) {
48
- if (provider === "anthropic")
49
- return ["setupToken"];
50
- if (provider === "openai-codex")
51
- return ["oauthAccessToken"];
52
- /* v8 ignore next -- branch tested via requiredCredentialKeys unit test @preserve */
53
- if (provider === "github-copilot")
54
- return ["githubToken"];
55
- if (provider === "minimax")
56
- return ["apiKey"];
57
- return ["apiKey", "endpoint", "deployment"];
48
+ return identity_1.PROVIDER_CREDENTIALS[provider].required;
58
49
  }
59
50
  function validateCredentials(provider, credentials) {
60
51
  const missing = requiredCredentialKeys(provider).filter((key) => {
@@ -77,6 +77,7 @@ class DaemonProcessManager {
77
77
  stopRequested: false,
78
78
  cooldownTimer: null,
79
79
  cooldownRetryCount: 0,
80
+ fastCrashCount: 0,
80
81
  snapshot: {
81
82
  name: agent.name,
82
83
  channel: agent.channel,
@@ -141,6 +142,12 @@ class DaemonProcessManager {
141
142
  env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
142
143
  stdio: ["ignore", "ignore", "ignore", "ipc"],
143
144
  });
145
+ /* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
146
+ if (!child) {
147
+ state.snapshot.status = "crashed";
148
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
149
+ return;
150
+ }
144
151
  state.process = child;
145
152
  state.snapshot.status = "running";
146
153
  state.snapshot.pid = child.pid ?? null;
@@ -251,6 +258,29 @@ class DaemonProcessManager {
251
258
  return;
252
259
  }
253
260
  state.snapshot.lastCrashAt = new Date(now).toISOString();
261
+ // Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
262
+ // a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
263
+ // After 3 consecutive fast crashes, stop retrying and mark as config-failed.
264
+ const FAST_CRASH_THRESHOLD_MS = 5000;
265
+ const FAST_CRASH_MAX = 3;
266
+ if (runDuration < FAST_CRASH_THRESHOLD_MS) {
267
+ state.fastCrashCount = state.fastCrashCount + 1;
268
+ if (state.fastCrashCount >= FAST_CRASH_MAX) {
269
+ state.snapshot.status = "crashed";
270
+ (0, runtime_1.emitNervesEvent)({
271
+ level: "error",
272
+ component: "daemon",
273
+ event: "daemon.agent_config_failure",
274
+ message: `agent crashed ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup — likely a configuration issue (missing credentials, bad provider). Not retrying. Fix the config and run \`ouro up\` to restart.`,
275
+ meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
276
+ });
277
+ return; // Don't schedule cooldown recovery — this needs human/agent intervention
278
+ }
279
+ }
280
+ else {
281
+ // Reset fast-crash counter on a stable run
282
+ state.fastCrashCount = 0;
283
+ }
254
284
  state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
255
285
  state.crashTimestamps.push(now);
256
286
  if (state.crashTimestamps.length > this.maxRestartsPerHour) {
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.HARNESS_CANONICAL_REPO_URL = exports.PROVIDER_CREDENTIALS = exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
36
+ exports.HARNESS_CANONICAL_REPO_URL = exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = exports.PROVIDER_CREDENTIALS = void 0;
37
37
  exports.buildDefaultAgentTemplate = buildDefaultAgentTemplate;
38
38
  exports.getAgentName = getAgentName;
39
39
  exports.getRepoRoot = getRepoRoot;
@@ -57,6 +57,14 @@ const os = __importStar(require("os"));
57
57
  const path = __importStar(require("path"));
58
58
  const runtime_1 = require("../nerves/runtime");
59
59
  const migrate_config_1 = require("./migrate-config");
60
+ /** Single source of truth for per-provider credential field names, env var mappings, and prompt labels. */
61
+ exports.PROVIDER_CREDENTIALS = {
62
+ anthropic: { required: ["setupToken"], envVars: { ANTHROPIC_API_KEY: "setupToken" }, promptLabels: { setupToken: "Anthropic setup-token" } },
63
+ "openai-codex": { required: ["oauthAccessToken"], envVars: { OPENAI_API_KEY: "oauthAccessToken" }, promptLabels: { oauthAccessToken: "OpenAI Codex OAuth token" } },
64
+ azure: { required: ["apiKey", "endpoint", "deployment"], envVars: { AZURE_OPENAI_API_KEY: "apiKey", AZURE_OPENAI_KEY: "apiKey", AZURE_OPENAI_ENDPOINT: "endpoint", AZURE_OPENAI_DEPLOYMENT: "deployment" }, promptLabels: { apiKey: "Azure API key", endpoint: "Azure endpoint", deployment: "Azure deployment" } },
65
+ minimax: { required: ["apiKey"], envVars: { MINIMAX_API_KEY: "apiKey" }, promptLabels: { apiKey: "MiniMax API key" } },
66
+ "github-copilot": { required: ["githubToken", "baseUrl"], envVars: { GH_TOKEN: "githubToken", GITHUB_TOKEN: "githubToken" }, promptLabels: { githubToken: "GitHub token" } },
67
+ };
60
68
  exports.DEFAULT_AGENT_CONTEXT = {
61
69
  maxTokens: 80000,
62
70
  contextMargin: 20,
@@ -208,17 +216,6 @@ function resolveOptionalAgentName(agentName) {
208
216
  function getAgentStateRoot(agentName) {
209
217
  return path.join(getAgentRoot(resolveOptionalAgentName(agentName)), "state");
210
218
  }
211
- /**
212
- * Required credential fields per provider.
213
- * Used by daemon config-check to validate secrets before spawning an agent.
214
- */
215
- exports.PROVIDER_CREDENTIALS = {
216
- azure: ["apiKey", "endpoint", "deployment"],
217
- minimax: ["apiKey"],
218
- anthropic: ["setupToken"],
219
- "openai-codex": ["oauthAccessToken"],
220
- "github-copilot": ["githubToken", "baseUrl"],
221
- };
222
219
  exports.HARNESS_CANONICAL_REPO_URL = "https://github.com/ouroborosbot/ouroboros.git";
223
220
  function getAgentRepoWorkspacesRoot(agentName) {
224
221
  return path.join(getAgentStateRoot(resolveOptionalAgentName(agentName)), "workspaces");
@@ -42,8 +42,15 @@ exports.advanceObligation = advanceObligation;
42
42
  exports.fulfillObligation = fulfillObligation;
43
43
  exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
44
44
  exports.enrichObligation = enrichObligation;
45
+ exports.generateObligationId = generateObligationId;
46
+ exports.getReturnObligationsDir = getReturnObligationsDir;
47
+ exports.createReturnObligation = createReturnObligation;
48
+ exports.readReturnObligation = readReturnObligation;
49
+ exports.advanceReturnObligation = advanceReturnObligation;
50
+ exports.listActiveReturnObligations = listActiveReturnObligations;
45
51
  const fs = __importStar(require("fs"));
46
52
  const path = __importStar(require("path"));
53
+ const identity_1 = require("./identity");
47
54
  const runtime_1 = require("../nerves/runtime");
48
55
  const sync_1 = require("./sync");
49
56
  function obligationsDir(agentRoot) {
@@ -223,3 +230,92 @@ function enrichObligation(agentRoot, id, meaning) {
223
230
  });
224
231
  return obligation;
225
232
  }
233
+ function generateObligationId(timestamp) {
234
+ return `${timestamp}-${Math.random().toString(36).slice(2, 10)}`;
235
+ }
236
+ function getReturnObligationsDir(agentName) {
237
+ return path.join((0, identity_1.getAgentRoot)(agentName), "arc", "obligations", "inner");
238
+ }
239
+ function createReturnObligation(agentName, obligation) {
240
+ const dir = getReturnObligationsDir(agentName);
241
+ fs.mkdirSync(dir, { recursive: true });
242
+ const filePath = path.join(dir, `${obligation.id}.json`);
243
+ fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf8");
244
+ (0, sync_1.trackSyncWrite)(filePath);
245
+ (0, runtime_1.emitNervesEvent)({
246
+ event: "mind.obligation_created",
247
+ component: "mind",
248
+ message: "return obligation created",
249
+ meta: {
250
+ obligationId: obligation.id,
251
+ origin: `${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}`,
252
+ status: obligation.status,
253
+ },
254
+ });
255
+ return filePath;
256
+ }
257
+ function readReturnObligation(agentName, obligationId) {
258
+ const dir = getReturnObligationsDir(agentName);
259
+ try {
260
+ const raw = fs.readFileSync(path.join(dir, `${obligationId}.json`), "utf-8");
261
+ return JSON.parse(raw);
262
+ }
263
+ catch {
264
+ return null;
265
+ }
266
+ }
267
+ function advanceReturnObligation(agentName, obligationId, update) {
268
+ const existing = readReturnObligation(agentName, obligationId);
269
+ if (!existing)
270
+ return null;
271
+ const updated = {
272
+ ...existing,
273
+ status: update.status,
274
+ ...(update.startedAt !== undefined ? { startedAt: update.startedAt } : {}),
275
+ ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
276
+ ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
277
+ };
278
+ const dir = getReturnObligationsDir(agentName);
279
+ const advancedFilePath = path.join(dir, `${obligationId}.json`);
280
+ fs.writeFileSync(advancedFilePath, JSON.stringify(updated, null, 2), "utf8");
281
+ (0, sync_1.trackSyncWrite)(advancedFilePath);
282
+ (0, runtime_1.emitNervesEvent)({
283
+ event: "mind.obligation_advanced",
284
+ component: "mind",
285
+ message: `obligation advanced to ${update.status}`,
286
+ meta: {
287
+ obligationId,
288
+ status: update.status,
289
+ ...(update.returnTarget ? { returnTarget: update.returnTarget } : {}),
290
+ },
291
+ });
292
+ return updated;
293
+ }
294
+ function listActiveReturnObligations(agentName) {
295
+ const dir = getReturnObligationsDir(agentName);
296
+ if (!fs.existsSync(dir))
297
+ return [];
298
+ let entries;
299
+ try {
300
+ entries = fs.readdirSync(dir);
301
+ }
302
+ catch {
303
+ return [];
304
+ }
305
+ const obligations = [];
306
+ for (const file of entries) {
307
+ if (!file.endsWith(".json"))
308
+ continue;
309
+ try {
310
+ const raw = fs.readFileSync(path.join(dir, file), "utf-8");
311
+ const parsed = JSON.parse(raw);
312
+ if (parsed.status === "queued" || parsed.status === "running") {
313
+ obligations.push(parsed);
314
+ }
315
+ }
316
+ catch {
317
+ // skip unparseable
318
+ }
319
+ }
320
+ return obligations.sort((a, b) => a.createdAt - b.createdAt);
321
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sanitizeErrorMessage = sanitizeErrorMessage;
4
4
  exports.pingProvider = pingProvider;
5
5
  exports.runHealthInventory = runHealthInventory;
6
+ const identity_1 = require("./identity");
6
7
  const anthropic_1 = require("./providers/anthropic");
7
8
  const azure_1 = require("./providers/azure");
8
9
  const minimax_1 = require("./providers/minimax");
@@ -42,25 +43,8 @@ function sanitizeErrorMessage(message) {
42
43
  return message;
43
44
  }
44
45
  function hasEmptyCredentials(provider, config) {
45
- switch (provider) {
46
- case "anthropic":
47
- return !config.setupToken;
48
- case "openai-codex":
49
- return !config.oauthAccessToken;
50
- case "minimax":
51
- return !config.apiKey;
52
- case "azure": {
53
- const azure = config;
54
- return !(azure.apiKey && azure.endpoint && azure.deployment);
55
- }
56
- case "github-copilot": {
57
- const copilot = config;
58
- return !(copilot.githubToken && copilot.baseUrl);
59
- }
60
- /* v8 ignore next 2 -- exhaustive: all providers handled above @preserve */
61
- default:
62
- return true;
63
- }
46
+ const record = config;
47
+ return identity_1.PROVIDER_CREDENTIALS[provider].required.some((key) => !record[key]);
64
48
  }
65
49
  function createRuntimeForPing(provider, _config) {
66
50
  // The provider constructors read credentials from the active config via getXxxConfig().
@@ -58,10 +58,9 @@ const coding_1 = require("./coding");
58
58
  const diary_1 = require("../mind/diary");
59
59
  const tasks_1 = require("./tasks");
60
60
  const pending_1 = require("../mind/pending");
61
- const obligations_1 = require("../mind/obligations");
61
+ const obligations_1 = require("../heart/obligations");
62
62
  const progress_story_1 = require("../heart/progress-story");
63
63
  const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
64
- const obligations_2 = require("../heart/obligations");
65
64
  const episodes_1 = require("../mind/episodes");
66
65
  const cares_1 = require("../heart/cares");
67
66
  const presence_1 = require("../heart/presence");
@@ -220,7 +219,7 @@ function readActiveWorkInnerState() {
220
219
  const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
221
220
  const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
222
221
  const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
223
- const storeObligationPending = (0, obligations_2.readPendingObligations)(agentRoot).length > 0;
222
+ const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
224
223
  return {
225
224
  status: dialogStatus.processing === "started" ? "running" : "idle",
226
225
  hasPending: dialogStatus.queue !== "clear",
@@ -269,7 +268,7 @@ async function buildToolActiveWorkFrame(ctx) {
269
268
  }
270
269
  const pendingObligations = (() => {
271
270
  try {
272
- return (0, obligations_2.readPendingObligations)(agentRoot);
271
+ return (0, obligations_1.readPendingObligations)(agentRoot);
273
272
  }
274
273
  catch {
275
274
  return [];
@@ -1296,7 +1295,7 @@ exports.baseToolDefinitions = [
1296
1295
  writePendingEnvelope(pendingDir, envelope);
1297
1296
  if (delegatedFrom) {
1298
1297
  try {
1299
- (0, obligations_2.createObligation)((0, identity_1.getAgentRoot)(), {
1298
+ (0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
1300
1299
  origin: {
1301
1300
  friendId: delegatedFrom.friendId,
1302
1301
  channel: delegatedFrom.channel,
@@ -1311,7 +1310,7 @@ exports.baseToolDefinitions = [
1311
1310
  }
1312
1311
  /* v8 ignore next -- obligationId always set when delegatedFrom is set (see generateObligationId above) @preserve */
1313
1312
  if (obligationId) {
1314
- (0, obligations_1.createObligation)(agentName, {
1313
+ (0, obligations_1.createReturnObligation)(agentName, {
1315
1314
  id: obligationId,
1316
1315
  origin: delegatedFrom,
1317
1316
  status: "queued",
@@ -48,8 +48,7 @@ const runtime_1 = require("../nerves/runtime");
48
48
  const guardrails_1 = require("./guardrails");
49
49
  const identity_1 = require("../heart/identity");
50
50
  const surface_tool_1 = require("../senses/surface-tool");
51
- const obligations_1 = require("../mind/obligations");
52
- const obligations_2 = require("../heart/obligations");
51
+ const obligations_1 = require("../heart/obligations");
53
52
  const session_activity_1 = require("../heart/session-activity");
54
53
  const path = __importStar(require("path"));
55
54
  function safeGetAgentRoot() {
@@ -198,7 +197,7 @@ const surfaceToolDefinition = {
198
197
  catch {
199
198
  return "unknown";
200
199
  } })();
201
- (0, obligations_1.advanceObligation)(name, obligationId, {
200
+ (0, obligations_1.advanceReturnObligation)(name, obligationId, {
202
201
  status: update.status,
203
202
  ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
204
203
  ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
@@ -213,9 +212,9 @@ const surfaceToolDefinition = {
213
212
  /* v8 ignore start -- heart obligation fulfillment: tested via surface-tool.test.ts @preserve */
214
213
  try {
215
214
  const agentRoot = (0, identity_1.getAgentRoot)();
216
- const heartObligation = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, origin);
215
+ const heartObligation = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, origin);
217
216
  if (heartObligation) {
218
- (0, obligations_2.fulfillObligation)(agentRoot, heartObligation.id);
217
+ (0, obligations_1.fulfillObligation)(agentRoot, heartObligation.id);
219
218
  }
220
219
  }
221
220
  catch {
@@ -151,6 +151,11 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
151
151
  const cursorRef = (0, react_1.useRef)(0);
152
152
  const historyIdx = (0, react_1.useRef)(-1);
153
153
  const savedInput = (0, react_1.useRef)("");
154
+ const tooltipTimerRef = (0, react_1.useRef)(null);
155
+ (0, react_1.useEffect)(() => () => {
156
+ if (tooltipTimerRef.current)
157
+ clearTimeout(tooltipTimerRef.current);
158
+ }, []);
154
159
  // Helper: update input and cursor together
155
160
  const updateInput = (text, pos) => {
156
161
  inputRef.current = text;
@@ -174,7 +179,9 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
174
179
  else if (action === "warn") {
175
180
  // tooltip handled below
176
181
  setTooltip("Ctrl-C again to exit");
177
- setTimeout(() => setTooltip(""), 3000);
182
+ if (tooltipTimerRef.current)
183
+ clearTimeout(tooltipTimerRef.current);
184
+ tooltipTimerRef.current = setTimeout(() => setTooltip(""), 3000);
178
185
  }
179
186
  // "abort" and "exit" are handled by the parent (cli.ts)
180
187
  }, [onCtrlC]);
@@ -191,7 +198,9 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
191
198
  updateInput("");
192
199
  historyIdx.current = -1;
193
200
  setTooltip("Esc again to clear");
194
- setTimeout(() => setTooltip(""), 2000);
201
+ if (tooltipTimerRef.current)
202
+ clearTimeout(tooltipTimerRef.current);
203
+ tooltipTimerRef.current = setTimeout(() => setTooltip(""), 2000);
195
204
  }
196
205
  else {
197
206
  setTooltip("");
@@ -55,7 +55,7 @@ const prompt_1 = require("../mind/prompt");
55
55
  const mcp_manager_1 = require("../repertoire/mcp-manager");
56
56
  const bundle_manifest_1 = require("../mind/bundle-manifest");
57
57
  const pending_1 = require("../mind/pending");
58
- const obligations_1 = require("../mind/obligations");
58
+ const obligations_1 = require("../heart/obligations");
59
59
  const attention_queue_1 = require("./attention-queue");
60
60
  const channel_1 = require("../mind/friends/channel");
61
61
  const trust_gate_1 = require("./trust-gate");
@@ -66,7 +66,6 @@ const runtime_1 = require("../nerves/runtime");
66
66
  const manager_1 = require("../heart/bridges/manager");
67
67
  const session_activity_1 = require("../heart/session-activity");
68
68
  const bluebubbles_1 = require("./bluebubbles");
69
- const obligations_2 = require("../heart/obligations");
70
69
  const habit_turn_message_1 = require("./habit-turn-message");
71
70
  const journal_index_1 = require("../mind/journal-index");
72
71
  const habit_parser_1 = require("../heart/daemon/habit-parser");
@@ -324,7 +323,7 @@ function advanceObligationQuietly(agentName, obligationId, update) {
324
323
  if (!obligationId)
325
324
  return;
326
325
  try {
327
- (0, obligations_1.advanceObligation)(agentName, obligationId, update);
326
+ (0, obligations_1.advanceReturnObligation)(agentName, obligationId, update);
328
327
  /* v8 ignore start -- best-effort: obligation fs errors must never block return routing @preserve */
329
328
  }
330
329
  catch {
@@ -352,14 +351,14 @@ async function routeDelegatedCompletion(agentRoot, agentName, completion, draine
352
351
  if (delegated.obligationStatus === "pending") {
353
352
  // Fulfill the persistent obligation in the store
354
353
  try {
355
- const pending = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, {
354
+ const pending = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, {
356
355
  friendId: delegatedFrom.friendId,
357
356
  channel: delegatedFrom.channel,
358
357
  key: delegatedFrom.key,
359
358
  });
360
359
  /* v8 ignore next 2 -- obligation fulfillment tested via obligations.test.ts; integration requires real disk state @preserve */
361
360
  if (pending) {
362
- (0, obligations_2.fulfillObligation)(agentRoot, pending.id);
361
+ (0, obligations_1.fulfillObligation)(agentRoot, pending.id);
363
362
  }
364
363
  }
365
364
  catch {
@@ -577,7 +576,7 @@ async function runInnerDialogTurn(options) {
577
576
  }
578
577
  else {
579
578
  // Unified path: gather context for ALL habits (heartbeat included)
580
- const obligations = (0, obligations_1.listActiveObligations)(agentName);
579
+ const obligations = (0, obligations_1.listActiveReturnObligations)(agentName);
581
580
  const nowMs = now().getTime();
582
581
  const staleObligations = obligations.map((o) => ({
583
582
  friendName: o.origin.friendId,
@@ -670,7 +669,7 @@ async function runInnerDialogTurn(options) {
670
669
  signal: options?.signal,
671
670
  /* v8 ignore start -- attention queue: callback invoked by pipeline during pending drain; tested via attention-queue unit tests @preserve */
672
671
  onPendingDrained: (drained) => {
673
- const outstandingObligations = (0, obligations_1.listActiveObligations)(agentName);
672
+ const outstandingObligations = (0, obligations_1.listActiveReturnObligations)(agentName);
674
673
  attentionQueue = (0, attention_queue_1.buildAttentionQueue)({
675
674
  drainedPending: drained,
676
675
  outstandingObligations,
@@ -57,7 +57,6 @@ const target_resolution_1 = require("../heart/target-resolution");
57
57
  const thoughts_1 = require("../heart/daemon/thoughts");
58
58
  const pending_1 = require("../mind/pending");
59
59
  const obligations_1 = require("../heart/obligations");
60
- const obligations_2 = require("../mind/obligations");
61
60
  const provider_failover_1 = require("../heart/provider-failover");
62
61
  const provider_ping_1 = require("../heart/provider-ping");
63
62
  const auth_flow_1 = require("../heart/daemon/auth-flow");
@@ -466,7 +465,7 @@ async function handleInboundTurn(input) {
466
465
  targetCandidates,
467
466
  innerReturnObligations: (() => {
468
467
  try {
469
- return (0, obligations_2.listActiveObligations)((0, identity_1.getAgentName)());
468
+ return (0, obligations_1.listActiveReturnObligations)((0, identity_1.getAgentName)());
470
469
  }
471
470
  catch {
472
471
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.201",
3
+ "version": "0.1.0-alpha.203",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -1,138 +0,0 @@
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.getObligationsDir = getObligationsDir;
37
- exports.generateObligationId = generateObligationId;
38
- exports.createObligation = createObligation;
39
- exports.readObligation = readObligation;
40
- exports.advanceObligation = advanceObligation;
41
- exports.listActiveObligations = listActiveObligations;
42
- const fs = __importStar(require("fs"));
43
- const path = __importStar(require("path"));
44
- const identity_1 = require("../heart/identity");
45
- const runtime_1 = require("../nerves/runtime");
46
- const sync_1 = require("../heart/sync");
47
- // ── Paths ────────────────────────────────────────────────────────
48
- function getObligationsDir(agentName) {
49
- return path.join((0, identity_1.getAgentRoot)(agentName), "arc", "obligations", "inner");
50
- }
51
- // ── ID generation ────────────────────────────────────────────────
52
- function generateObligationId(timestamp) {
53
- return `${timestamp}-${Math.random().toString(36).slice(2, 10)}`;
54
- }
55
- // ── Store operations ─────────────────────────────────────────────
56
- function createObligation(agentName, obligation) {
57
- const dir = getObligationsDir(agentName);
58
- fs.mkdirSync(dir, { recursive: true });
59
- const filePath = path.join(dir, `${obligation.id}.json`);
60
- fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf8");
61
- (0, sync_1.trackSyncWrite)(filePath);
62
- (0, runtime_1.emitNervesEvent)({
63
- event: "mind.obligation_created",
64
- component: "mind",
65
- message: "return obligation created",
66
- meta: {
67
- obligationId: obligation.id,
68
- origin: `${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}`,
69
- status: obligation.status,
70
- },
71
- });
72
- return filePath;
73
- }
74
- function readObligation(agentName, obligationId) {
75
- const dir = getObligationsDir(agentName);
76
- try {
77
- const raw = fs.readFileSync(path.join(dir, `${obligationId}.json`), "utf-8");
78
- return JSON.parse(raw);
79
- }
80
- catch {
81
- return null;
82
- }
83
- }
84
- function advanceObligation(agentName, obligationId, update) {
85
- const existing = readObligation(agentName, obligationId);
86
- if (!existing)
87
- return null;
88
- const updated = {
89
- ...existing,
90
- status: update.status,
91
- ...(update.startedAt !== undefined ? { startedAt: update.startedAt } : {}),
92
- ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
93
- ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
94
- };
95
- const dir = getObligationsDir(agentName);
96
- const advancedFilePath = path.join(dir, `${obligationId}.json`);
97
- fs.writeFileSync(advancedFilePath, JSON.stringify(updated, null, 2), "utf8");
98
- (0, sync_1.trackSyncWrite)(advancedFilePath);
99
- (0, runtime_1.emitNervesEvent)({
100
- event: "mind.obligation_advanced",
101
- component: "mind",
102
- message: `obligation advanced to ${update.status}`,
103
- meta: {
104
- obligationId,
105
- status: update.status,
106
- ...(update.returnTarget ? { returnTarget: update.returnTarget } : {}),
107
- },
108
- });
109
- return updated;
110
- }
111
- function listActiveObligations(agentName) {
112
- const dir = getObligationsDir(agentName);
113
- if (!fs.existsSync(dir))
114
- return [];
115
- let entries;
116
- try {
117
- entries = fs.readdirSync(dir);
118
- }
119
- catch {
120
- return [];
121
- }
122
- const obligations = [];
123
- for (const file of entries) {
124
- if (!file.endsWith(".json"))
125
- continue;
126
- try {
127
- const raw = fs.readFileSync(path.join(dir, file), "utf-8");
128
- const parsed = JSON.parse(raw);
129
- if (parsed.status === "queued" || parsed.status === "running") {
130
- obligations.push(parsed);
131
- }
132
- }
133
- catch {
134
- // skip unparseable
135
- }
136
- }
137
- return obligations.sort((a, b) => a.createdAt - b.createdAt);
138
- }