@pellux/goodvibes-agent 1.5.7 → 1.5.9

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 (77) hide show
  1. package/CHANGELOG.md +16 -3
  2. package/README.md +1 -1
  3. package/dist/package/main.js +309 -150
  4. package/package.json +1 -1
  5. package/src/agent/memory-prompt.ts +3 -3
  6. package/src/agent/operator-actions.ts +1 -1
  7. package/src/agent/prompt-context-receipts.ts +3 -3
  8. package/src/agent/session-registration.ts +1 -1
  9. package/src/agent/vibe-file.ts +5 -5
  10. package/src/audio/player.ts +91 -5
  11. package/src/audio/spoken-turn-controller.ts +281 -29
  12. package/src/audio/spoken-turn-wiring.ts +13 -2
  13. package/src/cli/local-library-command-shared.ts +1 -1
  14. package/src/cli/memory-command.ts +9 -6
  15. package/src/cli/resume-relaunch-notice.ts +1 -1
  16. package/src/cli/tui-startup.ts +1 -1
  17. package/src/config/credential-status.ts +1 -1
  18. package/src/config/index.ts +1 -1
  19. package/src/config/secrets.ts +1 -1
  20. package/src/core/conversation-rendering.ts +2 -2
  21. package/src/core/system-message-noise.ts +3 -3
  22. package/src/core/thinking-overlay.ts +1 -1
  23. package/src/input/agent-workspace-basic-command-editor-submission.ts +1 -1
  24. package/src/input/agent-workspace-basic-command-editors.ts +1 -1
  25. package/src/input/agent-workspace-calendar-connect-editor.ts +1 -1
  26. package/src/input/agent-workspace-calendar-oauth-editor.ts +1 -1
  27. package/src/input/agent-workspace-calendar-subscribe-editor.ts +1 -1
  28. package/src/input/agent-workspace-command-editor-engine.ts +2 -2
  29. package/src/input/agent-workspace-direct-editor-submission.ts +1 -1
  30. package/src/input/agent-workspace-email-connect-editor.ts +1 -1
  31. package/src/input/agent-workspace-live-counters.ts +1 -1
  32. package/src/input/agent-workspace-settings.ts +1 -1
  33. package/src/input/agent-workspace-snapshot-builders.ts +2 -2
  34. package/src/input/agent-workspace-snapshot-config.ts +1 -1
  35. package/src/input/agent-workspace-snapshot.ts +9 -9
  36. package/src/input/agent-workspace-types.ts +4 -4
  37. package/src/input/agent-workspace.ts +1 -1
  38. package/src/input/commands/calendar-connect-runtime.ts +1 -1
  39. package/src/input/commands/calendar-subscription-runtime.ts +2 -2
  40. package/src/input/commands/operator-actions-runtime.ts +1 -1
  41. package/src/input/commands/session-content.ts +1 -1
  42. package/src/input/commands/session-workflow.ts +1 -1
  43. package/src/input/feed-context-factory.ts +4 -4
  44. package/src/input/handler-feed.ts +9 -9
  45. package/src/input/handler.ts +1 -1
  46. package/src/input/panel-paste-flood-guard.ts +3 -3
  47. package/src/input/settings-modal.ts +1 -1
  48. package/src/main.ts +28 -33
  49. package/src/renderer/agent-workspace-context-lines.ts +1 -1
  50. package/src/renderer/startup-theme-probe.ts +1 -1
  51. package/src/renderer/status-glyphs.ts +2 -2
  52. package/src/renderer/terminal-bg-probe.ts +1 -1
  53. package/src/renderer/terminal-escapes.ts +3 -2
  54. package/src/renderer/theme-mode-config.ts +2 -2
  55. package/src/renderer/theme.ts +4 -4
  56. package/src/renderer/thinking.ts +1 -1
  57. package/src/renderer/ui-factory.ts +1 -1
  58. package/src/renderer/ui-primitives.ts +2 -2
  59. package/src/runtime/bootstrap-core.ts +2 -2
  60. package/src/runtime/bootstrap.ts +3 -3
  61. package/src/runtime/calendar-boot-refresh.ts +1 -1
  62. package/src/runtime/lan-scan-consent.ts +1 -1
  63. package/src/runtime/services.ts +7 -7
  64. package/src/runtime/terminal-output-guard.ts +1 -1
  65. package/src/runtime/ui-services.ts +1 -1
  66. package/src/runtime/unhandled-rejection-guard.ts +41 -0
  67. package/src/shell/agent-workspace-fullscreen.ts +1 -1
  68. package/src/shell/terminal-focus-mode.ts +9 -9
  69. package/src/shell/ui-openers.ts +1 -1
  70. package/src/tools/agent-harness-operator-methods.ts +1 -1
  71. package/src/tools/agent-harness-personal-ops-discovery.ts +2 -2
  72. package/src/tools/agent-harness-personal-ops-lanes.ts +1 -1
  73. package/src/tools/agent-harness-personal-ops-types.ts +1 -1
  74. package/src/tools/agent-harness-prompt-context.ts +3 -3
  75. package/src/tools/agent-local-registry-memory.ts +1 -1
  76. package/src/tools/agent-operator-method-tool.ts +1 -1
  77. package/src/version.ts +1 -1
@@ -459642,23 +459642,6 @@ function createSchema2(db) {
459642
459642
  `);
459643
459643
  db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_schedules_job_id ON knowledge_schedules(job_id)`);
459644
459644
  }
459645
- function getKnowledgeSchemaStatements() {
459646
- const statements = [];
459647
- createSchema2({
459648
- run(sql) {
459649
- const normalized = sql.trim();
459650
- if (normalized.length > 0)
459651
- statements.push(normalized);
459652
- }
459653
- });
459654
- return statements;
459655
- }
459656
- function renderKnowledgeSchemaSql() {
459657
- return `${getKnowledgeSchemaStatements().map((statement) => statement.endsWith(";") ? statement : `${statement};`).join(`
459658
-
459659
- `)}
459660
- `;
459661
- }
459662
459645
  function rowObject(columns, values2) {
459663
459646
  return Object.fromEntries(columns.map((column, index) => [column, values2[index]]));
459664
459647
  }
@@ -772974,9 +772957,6 @@ var init_ingest = __esm(() => {
772974
772957
  // node_modules/@pellux/goodvibes-sdk/dist/platform/knowledge/browser-history/index.js
772975
772958
  var init_browser_history = __esm(() => {
772976
772959
  init_ingest();
772977
- init_discover();
772978
- init_readers();
772979
- init_paths2();
772980
772960
  });
772981
772961
 
772982
772962
  // node_modules/graphql/jsutils/devAssert.mjs
@@ -790559,7 +790539,6 @@ var init_semantic = __esm(() => {
790559
790539
  init_llm();
790560
790540
  init_gap_repair();
790561
790541
  init_service4();
790562
- init_self_improvement();
790563
790542
  });
790564
790543
 
790565
790544
  // node_modules/@pellux/goodvibes-sdk/dist/platform/knowledge/home-graph/helpers.js
@@ -794528,40 +794507,8 @@ var init_map_view = __esm(() => {
794528
794507
  });
794529
794508
 
794530
794509
  // node_modules/@pellux/goodvibes-sdk/dist/platform/knowledge/home-graph/types.js
794531
- var HOME_GRAPH_NODE_KINDS, HOME_GRAPH_RELATIONS, HOME_GRAPH_CAPABILITIES;
794510
+ var HOME_GRAPH_CAPABILITIES;
794532
794511
  var init_types15 = __esm(() => {
794533
- HOME_GRAPH_NODE_KINDS = [
794534
- "ha_home",
794535
- "ha_entity",
794536
- "ha_device",
794537
- "ha_area",
794538
- "ha_automation",
794539
- "ha_script",
794540
- "ha_scene",
794541
- "ha_label",
794542
- "ha_integration",
794543
- "ha_room",
794544
- "ha_device_passport",
794545
- "ha_maintenance_item",
794546
- "ha_troubleshooting_case",
794547
- "ha_purchase",
794548
- "ha_network_node"
794549
- ];
794550
- HOME_GRAPH_RELATIONS = [
794551
- "controls",
794552
- "located_in",
794553
- "belongs_to_device",
794554
- "has_manual",
794555
- "has_receipt",
794556
- "has_warranty",
794557
- "has_issue",
794558
- "fixed_by",
794559
- "uses_battery",
794560
- "connected_via",
794561
- "part_of_network",
794562
- "mentioned_by",
794563
- "source_for"
794564
- ];
794565
794512
  HOME_GRAPH_CAPABILITIES = [
794566
794513
  "knowledge-space-isolation",
794567
794514
  "snapshot-sync",
@@ -795968,7 +795915,6 @@ var init_service5 = __esm(() => {
795968
795915
  var init_home_graph = __esm(() => {
795969
795916
  init_service5();
795970
795917
  init_extension();
795971
- init_types15();
795972
795918
  });
795973
795919
 
795974
795920
  // node_modules/@pellux/goodvibes-sdk/dist/platform/knowledge/store-refinement.js
@@ -798359,14 +798305,10 @@ var init_service6 = __esm(() => {
798359
798305
  var init_project_planning = __esm(() => {
798360
798306
  init_service6();
798361
798307
  init_helpers3();
798362
- init_readiness();
798363
798308
  });
798364
798309
 
798365
798310
  // node_modules/@pellux/goodvibes-sdk/dist/platform/knowledge/persistence.js
798366
- var init_persistence = __esm(() => {
798367
- init_store_schema();
798368
- init_store_load();
798369
- });
798311
+ var init_persistence = () => {};
798370
798312
 
798371
798313
  // node_modules/@pellux/goodvibes-sdk/dist/platform/knowledge/projections.js
798372
798314
  class KnowledgeProjectionService {
@@ -801871,21 +801813,21 @@ var init_service7 = __esm(() => {
801871
801813
  var exports_knowledge = {};
801872
801814
  __export(exports_knowledge, {
801873
801815
  withKnowledgeSpace: () => withKnowledgeSpace,
801874
- uniqKnowledgeValues: () => uniq2,
801875
- stabilizeKnowledgeText: () => stableText,
801876
- runKnowledgeSemanticSelfImprovement: () => runKnowledgeSemanticSelfImprovement,
801816
+ uniqKnowledgeValues: () => uniq3,
801817
+ stabilizeKnowledgeText: () => stableText2,
801818
+ runKnowledgeSemanticSelfImprovement: () => runKnowledgeSemanticSelfImprovement2,
801877
801819
  resolveProjectPlanningSpace: () => resolveProjectPlanningSpace,
801878
- resolveKnowledgeDbPathFromControlPlaneDir: () => resolveKnowledgeDbPathFromControlPlaneDir,
801820
+ resolveKnowledgeDbPathFromControlPlaneDir: () => resolveKnowledgeDbPathFromControlPlaneDir2,
801879
801821
  renderPacket: () => renderPacket,
801880
801822
  renderKnowledgeSchemaSql: () => renderKnowledgeSchemaSql,
801881
801823
  renderKnowledgeMap: () => renderKnowledgeMap,
801882
801824
  readKnowledgeSearchText: () => readKnowledgeSearchText,
801883
- readBrowserKnowledgeProfile: () => readBrowserKnowledgeProfile,
801825
+ readBrowserKnowledgeProfile: () => readBrowserKnowledgeProfile2,
801884
801826
  projectPlanningSourceId: () => projectPlanningSourceId,
801885
801827
  projectPlanningProjectIdFromPath: () => projectPlanningProjectIdFromPath,
801886
801828
  projectPlanningCanonicalUri: () => projectPlanningCanonicalUri,
801887
801829
  projectKnowledgeSpaceId: () => projectKnowledgeSpaceId,
801888
- parseKnowledgeJsonValue: () => parseJsonValue,
801830
+ parseKnowledgeJsonValue: () => parseJsonValue2,
801889
801831
  normalizeProjectId: () => normalizeProjectId,
801890
801832
  normalizeKnowledgeSpaceId: () => normalizeKnowledgeSpaceId,
801891
801833
  normalizeHomeAssistantInstallationId: () => normalizeHomeAssistantInstallationId,
@@ -801893,12 +801835,12 @@ __export(exports_knowledge, {
801893
801835
  materializeGeneratedKnowledgeProjection: () => materializeGeneratedKnowledgeProjection,
801894
801836
  looksLikeRawPdfPayload: () => looksLikeRawPdfPayload,
801895
801837
  looksBinaryLikeText: () => looksBinaryLikeText,
801896
- loadKnowledgeStoreSnapshot: () => loadKnowledgeStoreSnapshot,
801838
+ loadKnowledgeStoreSnapshot: () => loadKnowledgeStoreSnapshot2,
801897
801839
  listGeneratedKnowledgePages: () => listGeneratedKnowledgePages,
801898
- listBrowserKinds: () => listBrowserKinds,
801840
+ listBrowserKinds: () => listBrowserKinds2,
801899
801841
  knowledgeSpaceMetadata: () => knowledgeSpaceMetadata,
801900
801842
  knowledgePageSourceWeight: () => knowledgePageSourceWeight,
801901
- knowledgeNowMs: () => nowMs,
801843
+ knowledgeNowMs: () => nowMs2,
801902
801844
  knowledgeExtractionNeedsRefresh: () => knowledgeExtractionNeedsRefresh,
801903
801845
  isUsefulKnowledgePageSourceCandidate: () => isUsefulKnowledgePageSourceCandidate,
801904
801846
  isUsefulKnowledgePageSource: () => isUsefulKnowledgePageSource,
@@ -801918,12 +801860,12 @@ __export(exports_knowledge, {
801918
801860
  generatedKnowledgeSourceId: () => generatedKnowledgeSourceId,
801919
801861
  generatedKnowledgeCanonicalUri: () => generatedKnowledgeCanonicalUri,
801920
801862
  extractKnowledgeArtifact: () => extractKnowledgeArtifact,
801921
- evaluateProjectPlanningReadiness: () => evaluateProjectPlanningReadiness,
801922
- discoverBrowserKnowledgeProfiles: () => discoverBrowserKnowledgeProfiles,
801863
+ evaluateProjectPlanningReadiness: () => evaluateProjectPlanningReadiness2,
801864
+ discoverBrowserKnowledgeProfiles: () => discoverBrowserKnowledgeProfiles2,
801923
801865
  createWebKnowledgeGapRepairer: () => createWebKnowledgeGapRepairer,
801924
801866
  createProviderBackedKnowledgeSemanticLlm: () => createProviderBackedKnowledgeSemanticLlm,
801925
801867
  createMemoryApi: () => createMemoryApi,
801926
- createKnowledgeSchema: () => createSchema2,
801868
+ createKnowledgeSchema: () => createSchema3,
801927
801869
  createKnowledgeApi: () => createKnowledgeApi,
801928
801870
  createDefaultKnowledgeConnectorRegistry: () => createDefaultKnowledgeConnectorRegistry,
801929
801871
  compareKnowledgePageSources: () => compareKnowledgePageSources,
@@ -804387,7 +804329,7 @@ var init_delivery_manager = __esm(() => {
804387
804329
  });
804388
804330
 
804389
804331
  // node_modules/@pellux/goodvibes-sdk/dist/platform/automation/scheduler-capacity.js
804390
- function computeSchedulerCapacity(slotsTotal, runs, nowMs2 = Date.now()) {
804332
+ function computeSchedulerCapacity(slotsTotal, runs, nowMs3 = Date.now()) {
804391
804333
  let slotsInUse = 0;
804392
804334
  const queuedRuns = [];
804393
804335
  for (const run7 of runs) {
@@ -804397,7 +804339,7 @@ function computeSchedulerCapacity(slotsTotal, runs, nowMs2 = Date.now()) {
804397
804339
  queuedRuns.push(run7);
804398
804340
  }
804399
804341
  const queueDepth = queuedRuns.length;
804400
- const oldestQueuedAgeMs = queueDepth > 0 ? nowMs2 - Math.min(...queuedRuns.map((r6) => r6.queuedAt)) : null;
804342
+ const oldestQueuedAgeMs = queueDepth > 0 ? nowMs3 - Math.min(...queuedRuns.map((r6) => r6.queuedAt)) : null;
804401
804343
  return { slotsTotal, slotsInUse, queueDepth, oldestQueuedAgeMs };
804402
804344
  }
804403
804345
 
@@ -830785,7 +830727,7 @@ var createStyledCell = (char, overrides = {}) => ({
830785
830727
  // src/version.ts
830786
830728
  import { readFileSync } from "fs";
830787
830729
  import { join } from "path";
830788
- var _version = "1.5.7";
830730
+ var _version = "1.5.9";
830789
830731
  try {
830790
830732
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf-8"));
830791
830733
  _version = typeof pkg.version === "string" ? pkg.version : _version;
@@ -855898,7 +855840,7 @@ var WORK_PLAN_STATUSES = [
855898
855840
  "failed",
855899
855841
  "cancelled"
855900
855842
  ];
855901
- function nowMs2() {
855843
+ function nowMs3() {
855902
855844
  return Date.now();
855903
855845
  }
855904
855846
  function isObject4(value) {
@@ -856004,7 +855946,7 @@ class WorkPlanStore {
856004
855946
  if (!normalizedTitle)
856005
855947
  throw new Error("Work plan item title is required.");
856006
855948
  const plan = this.readPlan();
856007
- const time4 = nowMs2();
855949
+ const time4 = nowMs3();
856008
855950
  const item = {
856009
855951
  id: createItemId(),
856010
855952
  title: normalizedTitle,
@@ -856028,7 +855970,7 @@ class WorkPlanStore {
856028
855970
  updateItem(idOrPrefix, patch2) {
856029
855971
  const plan = this.readPlan();
856030
855972
  const item = this.resolveItem(plan, idOrPrefix);
856031
- const time4 = nowMs2();
855973
+ const time4 = nowMs3();
856032
855974
  const nextStatus = patch2.status ?? item.status;
856033
855975
  const next = this.pruneItem({
856034
855976
  ...item,
@@ -856061,7 +856003,7 @@ class WorkPlanStore {
856061
856003
  removeItem(idOrPrefix) {
856062
856004
  const plan = this.readPlan();
856063
856005
  const item = this.resolveItem(plan, idOrPrefix);
856064
- const time4 = nowMs2();
856006
+ const time4 = nowMs3();
856065
856007
  const remaining = plan.items.filter((candidate) => candidate.id !== item.id);
856066
856008
  this.writePlan({
856067
856009
  ...plan,
@@ -856081,7 +856023,7 @@ class WorkPlanStore {
856081
856023
  ...plan,
856082
856024
  items: remaining,
856083
856025
  activeItemId: remaining[0]?.id,
856084
- updatedAt: nowMs2()
856026
+ updatedAt: nowMs3()
856085
856027
  });
856086
856028
  return removed;
856087
856029
  }
@@ -856120,7 +856062,7 @@ class WorkPlanStore {
856120
856062
  const parsed = JSON.parse(raw);
856121
856063
  if (!isObject4(parsed))
856122
856064
  return this.createEmptyPlan();
856123
- const time4 = nowMs2();
856065
+ const time4 = nowMs3();
856124
856066
  const createdAt = typeof parsed.createdAt === "number" ? parsed.createdAt : time4;
856125
856067
  const updatedAt = typeof parsed.updatedAt === "number" ? parsed.updatedAt : createdAt;
856126
856068
  const items = Array.isArray(parsed.items) ? parsed.items.map((item) => normalizeItem(item, createdAt)).filter((item) => item !== null) : [];
@@ -856139,7 +856081,7 @@ class WorkPlanStore {
856139
856081
  };
856140
856082
  }
856141
856083
  createEmptyPlan() {
856142
- const time4 = nowMs2();
856084
+ const time4 = nowMs3();
856143
856085
  return {
856144
856086
  id: createPlanId(this.options.projectId, this.options.projectRoot),
856145
856087
  projectId: this.options.projectId,
@@ -901803,9 +901745,9 @@ Run goodvibes-agent skills discover to inspect available skill files.`, 1);
901803
901745
  }
901804
901746
 
901805
901747
  // src/cli/memory-command.ts
901748
+ init_state();
901806
901749
  import { mkdirSync as mkdirSync83, readFileSync as readFileSync105, writeFileSync as writeFileSync72 } from "fs";
901807
901750
  import { dirname as dirname80, resolve as resolve43 } from "path";
901808
- init_state();
901809
901751
  var VALID_CLASSES2 = ["decision", "constraint", "incident", "pattern", "fact", "risk", "runbook", "architecture", "ownership"];
901810
901752
  var VALID_SCOPES4 = ["session", "project", "team"];
901811
901753
  var VALID_REVIEW_STATES3 = ["fresh", "reviewed", "stale", "contradicted"];
@@ -901939,10 +901881,7 @@ function timestamp(value) {
901939
901881
  return new Date(value).toISOString().slice(0, 19).replace("T", " ");
901940
901882
  }
901941
901883
  function memoryDbPath(runtime2) {
901942
- return createShellPathService2({
901943
- workingDirectory: runtime2.workingDirectory,
901944
- homeDirectory: runtime2.homeDirectory
901945
- }).resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, "memory.sqlite");
901884
+ return resolveCanonicalMemoryDbPath(runtime2.homeDirectory);
901946
901885
  }
901947
901886
  async function withMemory(runtime2, fn) {
901948
901887
  const path7 = memoryDbPath(runtime2);
@@ -967154,7 +967093,7 @@ class LocalStreamingAudioPlayer {
967154
967093
  activeProcess = null;
967155
967094
  spawnProcess;
967156
967095
  constructor(options = {}) {
967157
- this.command = resolveStreamingAudioPlayerCommand(options.env ?? process.env);
967096
+ this.command = options.command !== undefined ? options.command : resolveStreamingAudioPlayerCommand(options.env ?? process.env);
967158
967097
  this.spawnProcess = options.spawnProcess ?? defaultSpawnProcess;
967159
967098
  }
967160
967099
  get label() {
@@ -967181,6 +967120,9 @@ class LocalStreamingAudioPlayer {
967181
967120
  };
967182
967121
  options.signal?.addEventListener("abort", abort6, { once: true });
967183
967122
  try {
967123
+ await awaitReady(proc, options.signal);
967124
+ if (options.signal?.aborted)
967125
+ return;
967184
967126
  for await (const chunk of chunks) {
967185
967127
  if (options.signal?.aborted)
967186
967128
  break;
@@ -967188,7 +967130,11 @@ class LocalStreamingAudioPlayer {
967188
967130
  continue;
967189
967131
  await writeStdin(proc, chunk.data);
967190
967132
  }
967191
- proc.stdin.end();
967133
+ if (options.signal?.aborted)
967134
+ return;
967135
+ try {
967136
+ proc.stdin.end();
967137
+ } catch {}
967192
967138
  await waitForExit(proc);
967193
967139
  } finally {
967194
967140
  options.signal?.removeEventListener("abort", abort6);
@@ -967208,13 +967154,30 @@ class LocalStreamingAudioPlayer {
967208
967154
  proc.kill("SIGTERM");
967209
967155
  } catch {}
967210
967156
  }
967157
+ waitForDrain(timeoutMs) {
967158
+ const proc = this.activeProcess;
967159
+ if (!proc)
967160
+ return Promise.resolve();
967161
+ return new Promise((resolve45) => {
967162
+ let settled = false;
967163
+ const settle = () => {
967164
+ if (settled)
967165
+ return;
967166
+ settled = true;
967167
+ clearTimeout(timer);
967168
+ resolve45();
967169
+ };
967170
+ const timer = setTimeout(settle, timeoutMs);
967171
+ proc.once("close", settle);
967172
+ });
967173
+ }
967211
967174
  }
967212
967175
  function resolveStreamingAudioPlayerCommand(env2 = process.env) {
967213
967176
  const mpv = findExecutable("mpv", env2);
967214
967177
  if (mpv) {
967215
967178
  return {
967216
967179
  command: mpv,
967217
- args: ["--no-terminal", "--really-quiet", "--force-window=no", "--cache=no", "-"],
967180
+ args: ["--no-terminal", "--really-quiet", "--force-window=no", "-"],
967218
967181
  label: "mpv"
967219
967182
  };
967220
967183
  }
@@ -967222,19 +967185,41 @@ function resolveStreamingAudioPlayerCommand(env2 = process.env) {
967222
967185
  if (ffplay) {
967223
967186
  return {
967224
967187
  command: ffplay,
967225
- args: ["-nodisp", "-autoexit", "-loglevel", "error", "-i", "pipe:0"],
967188
+ args: FFPLAY_BASE_ARGS,
967226
967189
  label: "ffplay"
967227
967190
  };
967228
967191
  }
967229
967192
  return null;
967230
967193
  }
967194
+ var FFPLAY_APAD = ["-af", "apad=pad_dur=0.3"];
967195
+ var FFPLAY_BASE_ARGS = ["-nodisp", "-autoexit", "-loglevel", "error", ...FFPLAY_APAD, "-i", "pipe:0"];
967231
967196
  function buildPlayerArgs(command8, format3) {
967232
967197
  if (command8.label !== "ffplay" || !format3)
967233
967198
  return command8.args;
967234
967199
  const normalized3 = format3.trim().toLowerCase();
967235
967200
  if (!normalized3 || normalized3.includes("/"))
967236
967201
  return command8.args;
967237
- return ["-nodisp", "-autoexit", "-loglevel", "error", "-f", normalized3, "-i", "pipe:0"];
967202
+ return ["-nodisp", "-autoexit", "-loglevel", "error", ...FFPLAY_APAD, "-f", normalized3, "-i", "pipe:0"];
967203
+ }
967204
+ function awaitReady(proc, signal) {
967205
+ if (signal?.aborted)
967206
+ return Promise.resolve();
967207
+ return new Promise((resolve45, reject) => {
967208
+ let settled = false;
967209
+ const settle = (action2) => {
967210
+ if (settled)
967211
+ return;
967212
+ settled = true;
967213
+ signal?.removeEventListener("abort", onAbort);
967214
+ action2();
967215
+ };
967216
+ const onSpawn = () => settle(resolve45);
967217
+ const onError2 = (error73) => settle(() => reject(error73 instanceof Error ? error73 : new Error(String(error73))));
967218
+ const onAbort = () => settle(resolve45);
967219
+ proc.once("spawn", onSpawn);
967220
+ proc.once("error", onError2);
967221
+ signal?.addEventListener("abort", onAbort, { once: true });
967222
+ });
967238
967223
  }
967239
967224
  function findExecutable(name51, env2) {
967240
967225
  const pathValue = env2.PATH ?? "";
@@ -967372,6 +967357,10 @@ function normalizeSpeechText(text) {
967372
967357
  }
967373
967358
 
967374
967359
  // src/audio/spoken-turn-controller.ts
967360
+ var SYNTHESIS_PIPELINE_WINDOW = 2;
967361
+ var SYNTHESIS_MERGE_MAX_CHARS = 1500;
967362
+ var SYNTHESIS_RETRY_DELAYS_MS = [1000, 2500];
967363
+
967375
967364
  class SpokenTurnController {
967376
967365
  pendingPrompt = null;
967377
967366
  activeTurnId = null;
@@ -967381,6 +967370,12 @@ class SpokenTurnController {
967381
967370
  abortControllers = new Set;
967382
967371
  timer = null;
967383
967372
  errorReportedForTurn = false;
967373
+ noPlayerNoticed = false;
967374
+ pendingTexts = [];
967375
+ pipelineDepth = 0;
967376
+ pipelineGeneration = 0;
967377
+ pumpScheduled = false;
967378
+ completedTurnId = null;
967384
967379
  voiceService;
967385
967380
  configManager;
967386
967381
  player;
@@ -967388,6 +967383,8 @@ class SpokenTurnController {
967388
967383
  now;
967389
967384
  setIntervalImpl;
967390
967385
  clearIntervalImpl;
967386
+ setTimeoutImpl;
967387
+ clearTimeoutImpl;
967391
967388
  constructor(options) {
967392
967389
  this.voiceService = options.voiceService;
967393
967390
  this.configManager = options.configManager;
@@ -967396,6 +967393,8 @@ class SpokenTurnController {
967396
967393
  this.now = options.now ?? (() => Date.now());
967397
967394
  this.setIntervalImpl = options.setInterval ?? setInterval;
967398
967395
  this.clearIntervalImpl = options.clearInterval ?? clearInterval;
967396
+ this.setTimeoutImpl = options.setTimeout ?? setTimeout;
967397
+ this.clearTimeoutImpl = options.clearTimeout ?? clearTimeout;
967399
967398
  }
967400
967399
  submitNextTurn(prompt) {
967401
967400
  const normalized3 = prompt.trim();
@@ -967403,26 +967402,46 @@ class SpokenTurnController {
967403
967402
  return false;
967404
967403
  this.stop();
967405
967404
  if (!this.player.available) {
967406
- this.notify?.("[TTS] Text response will continue, but live audio is unavailable. Install mpv or ffplay.");
967405
+ if (!this.noPlayerNoticed) {
967406
+ this.noPlayerNoticed = true;
967407
+ this.notify?.("[TTS] Text response will continue, but live audio is unavailable. Install mpv or ffplay.");
967408
+ }
967407
967409
  return false;
967408
967410
  }
967411
+ this.noPlayerNoticed = false;
967409
967412
  this.pendingPrompt = normalized3;
967410
967413
  return true;
967411
967414
  }
967412
967415
  stop(message) {
967416
+ const wasActive = this.pendingPrompt !== null || this.activeTurnId !== null || this.chunker !== null || this.abortControllers.size > 0;
967413
967417
  this.pendingPrompt = null;
967414
967418
  this.activeTurnId = null;
967415
967419
  this.chunker?.reset();
967416
967420
  this.chunker = null;
967417
967421
  this.stopTimer();
967422
+ this.resetPipeline();
967418
967423
  for (const controller of this.abortControllers)
967419
967424
  controller.abort();
967420
967425
  this.abortControllers.clear();
967421
967426
  this.player.stop();
967422
967427
  this.playbackChain = Promise.resolve();
967423
967428
  this.errorReportedForTurn = false;
967424
- if (message)
967429
+ if (message && wasActive)
967425
967430
  this.notify?.(`[TTS] ${message}`);
967431
+ return wasActive;
967432
+ }
967433
+ async stopForExit(drainTimeoutMs = 2000) {
967434
+ this.pendingPrompt = null;
967435
+ this.activeTurnId = null;
967436
+ this.chunker?.reset();
967437
+ this.chunker = null;
967438
+ this.stopTimer();
967439
+ this.resetPipeline();
967440
+ for (const controller of this.abortControllers)
967441
+ controller.abort();
967442
+ this.abortControllers.clear();
967443
+ await this.player.waitForDrain(drainTimeoutMs);
967444
+ this.stop();
967426
967445
  }
967427
967446
  handleTurnEvent(event) {
967428
967447
  if (event.type === "TURN_SUBMITTED") {
@@ -967432,7 +967451,7 @@ class SpokenTurnController {
967432
967451
  if (!this.activeTurnId || event.turnId !== this.activeTurnId)
967433
967452
  return;
967434
967453
  if (event.type === "STREAM_DELTA") {
967435
- this.enqueueChunks(this.chunker?.push(event.content) ?? []);
967454
+ this.queueTexts(this.chunker?.push(event.content) ?? []);
967436
967455
  return;
967437
967456
  }
967438
967457
  if (event.type === "STREAM_END") {
@@ -967457,29 +967476,30 @@ class SpokenTurnController {
967457
967476
  this.errorReportedForTurn = false;
967458
967477
  this.chunker = new TtsTextChunker({ now: this.now });
967459
967478
  this.playbackChain = Promise.resolve();
967479
+ this.resetPipeline();
967460
967480
  this.startTimer();
967461
967481
  this.notify?.(`[TTS] Live playback queued through ${this.player.label}.`);
967462
967482
  }
967463
967483
  finishTurn(turnId) {
967464
967484
  if (turnId !== this.activeTurnId)
967465
967485
  return;
967466
- this.enqueueChunks(this.chunker?.flushAll() ?? []);
967486
+ this.queueTexts(this.chunker?.flushAll() ?? []);
967467
967487
  this.stopTimer();
967468
- const chain4 = this.playbackChain;
967469
- chain4.finally(() => {
967470
- if (this.activeTurnId !== turnId)
967471
- return;
967472
- this.activeTurnId = null;
967473
- this.chunker = null;
967474
- this.abortControllers.clear();
967475
- }).catch(() => {});
967488
+ this.completedTurnId = turnId;
967489
+ this.maybeReleaseTurn();
967490
+ }
967491
+ resetPipeline() {
967492
+ this.pendingTexts = [];
967493
+ this.pipelineDepth = 0;
967494
+ this.pipelineGeneration++;
967495
+ this.completedTurnId = null;
967476
967496
  }
967477
967497
  startTimer() {
967478
967498
  this.stopTimer();
967479
967499
  this.timer = this.setIntervalImpl(() => {
967480
967500
  if (!this.activeTurnId || !this.chunker)
967481
967501
  return;
967482
- this.enqueueChunks(this.chunker.flushDue());
967502
+ this.queueTexts(this.chunker.flushDue());
967483
967503
  }, 250);
967484
967504
  }
967485
967505
  stopTimer() {
@@ -967488,42 +967508,136 @@ class SpokenTurnController {
967488
967508
  this.clearIntervalImpl(this.timer);
967489
967509
  this.timer = null;
967490
967510
  }
967491
- enqueueChunks(chunks) {
967511
+ queueTexts(chunks) {
967492
967512
  for (const chunk of chunks) {
967493
- this.enqueueChunk(chunk);
967513
+ if (chunk.trim())
967514
+ this.pendingTexts.push(chunk);
967515
+ }
967516
+ if (this.pendingTexts.length > 0)
967517
+ this.schedulePump();
967518
+ }
967519
+ schedulePump() {
967520
+ if (this.pumpScheduled)
967521
+ return;
967522
+ this.pumpScheduled = true;
967523
+ queueMicrotask(() => {
967524
+ this.pumpScheduled = false;
967525
+ this.pump();
967526
+ });
967527
+ }
967528
+ pump() {
967529
+ while (this.activeTurnId && this.pendingTexts.length > 0 && this.pipelineDepth < SYNTHESIS_PIPELINE_WINDOW) {
967530
+ this.dispatchChunk(this.takeMergedText());
967494
967531
  }
967532
+ this.maybeReleaseTurn();
967495
967533
  }
967496
- enqueueChunk(text) {
967534
+ takeMergedText() {
967535
+ let merged = "";
967536
+ while (this.pendingTexts.length > 0) {
967537
+ const next = this.pendingTexts[0];
967538
+ if (!merged && next.length > SYNTHESIS_MERGE_MAX_CHARS) {
967539
+ const cut = findSplitIndex(next, SYNTHESIS_MERGE_MAX_CHARS);
967540
+ this.pendingTexts[0] = next.slice(cut).trim();
967541
+ return next.slice(0, cut).trim();
967542
+ }
967543
+ if (merged && merged.length + 1 + next.length > SYNTHESIS_MERGE_MAX_CHARS)
967544
+ break;
967545
+ merged = merged ? `${merged} ${next}` : next;
967546
+ this.pendingTexts.shift();
967547
+ }
967548
+ return merged;
967549
+ }
967550
+ dispatchChunk(text) {
967497
967551
  const turnId = this.activeTurnId;
967498
967552
  if (!turnId || !text.trim())
967499
967553
  return;
967500
967554
  const sequence = ++this.chunkSequence;
967555
+ const generation = this.pipelineGeneration;
967556
+ this.pipelineDepth++;
967501
967557
  const abortController = new AbortController;
967502
967558
  this.abortControllers.add(abortController);
967503
- const resultPromise = this.synthesize(text, turnId, sequence, abortController.signal).then((result2) => ({ ok: true, result: result2 })).catch((error73) => ({ ok: false, error: error73 }));
967559
+ const resultPromise = this.synthesizeWithRetry(text, turnId, sequence, abortController.signal).then((result2) => ({ ok: true, result: result2 })).catch((error73) => ({ ok: false, error: error73 }));
967504
967560
  this.playbackChain = this.playbackChain.then(async () => {
967505
- if (abortController.signal.aborted)
967506
- return;
967507
- const result2 = await resultPromise;
967508
- this.abortControllers.delete(abortController);
967509
- if (!result2.ok) {
967510
- this.reportError(result2.error);
967511
- return;
967561
+ try {
967562
+ if (abortController.signal.aborted) {
967563
+ this.abortControllers.delete(abortController);
967564
+ return;
967565
+ }
967566
+ const result2 = await resultPromise;
967567
+ this.abortControllers.delete(abortController);
967568
+ if (abortController.signal.aborted)
967569
+ return;
967570
+ if (!result2.ok) {
967571
+ this.reportSkippedChunk(result2.error);
967572
+ return;
967573
+ }
967574
+ await this.player.play(result2.result.chunks, {
967575
+ format: String(result2.result.format ?? "mp3"),
967576
+ signal: abortController.signal
967577
+ });
967578
+ } finally {
967579
+ this.releasePipelineSlot(generation);
967512
967580
  }
967513
- await this.player.play(result2.result.chunks, {
967514
- format: String(result2.result.format ?? "mp3"),
967515
- signal: abortController.signal
967516
- });
967517
967581
  }).catch((error73) => {
967518
967582
  this.abortControllers.delete(abortController);
967519
967583
  this.reportError(error73);
967520
967584
  });
967521
967585
  }
967586
+ releasePipelineSlot(generation) {
967587
+ if (generation !== this.pipelineGeneration)
967588
+ return;
967589
+ this.pipelineDepth = Math.max(0, this.pipelineDepth - 1);
967590
+ if (this.pendingTexts.length > 0) {
967591
+ this.schedulePump();
967592
+ return;
967593
+ }
967594
+ this.maybeReleaseTurn();
967595
+ }
967596
+ maybeReleaseTurn() {
967597
+ if (!this.completedTurnId || this.completedTurnId !== this.activeTurnId)
967598
+ return;
967599
+ if (this.pendingTexts.length > 0 || this.pipelineDepth > 0 || this.pumpScheduled)
967600
+ return;
967601
+ this.activeTurnId = null;
967602
+ this.completedTurnId = null;
967603
+ this.chunker = null;
967604
+ this.abortControllers.clear();
967605
+ }
967606
+ async synthesizeWithRetry(text, turnId, sequence, signal) {
967607
+ for (let attempt = 0;; attempt++) {
967608
+ try {
967609
+ return await this.synthesize(text, turnId, sequence, signal);
967610
+ } catch (error73) {
967611
+ const retryable = attempt < SYNTHESIS_RETRY_DELAYS_MS.length && !signal.aborted && isTransientSynthesisError(error73);
967612
+ if (!retryable)
967613
+ throw error73;
967614
+ await this.delay(SYNTHESIS_RETRY_DELAYS_MS[attempt], signal);
967615
+ }
967616
+ }
967617
+ }
967618
+ delay(ms, signal) {
967619
+ return new Promise((resolve45, reject) => {
967620
+ if (signal.aborted) {
967621
+ reject(new Error("Synthesis retry cancelled"));
967622
+ return;
967623
+ }
967624
+ const timer = this.setTimeoutImpl(() => {
967625
+ signal.removeEventListener("abort", onAbort);
967626
+ resolve45();
967627
+ }, ms);
967628
+ const onAbort = () => {
967629
+ this.clearTimeoutImpl(timer);
967630
+ reject(new Error("Synthesis retry cancelled"));
967631
+ };
967632
+ signal.addEventListener("abort", onAbort, { once: true });
967633
+ });
967634
+ }
967522
967635
  synthesize(text, turnId, sequence, signal) {
967523
967636
  return this.voiceService.synthesizeStream(readOptionalConfigString2(this.configManager, "tts.provider"), {
967524
967637
  text,
967525
967638
  voiceId: readOptionalConfigString2(this.configManager, "tts.voice"),
967526
967639
  format: "mp3",
967640
+ speed: readOptionalConfigNumber(this.configManager, "tts.speed"),
967527
967641
  signal,
967528
967642
  metadata: {
967529
967643
  source: "goodvibes-agent",
@@ -967533,6 +967647,12 @@ class SpokenTurnController {
967533
967647
  }
967534
967648
  });
967535
967649
  }
967650
+ reportSkippedChunk(error73) {
967651
+ if (this.errorReportedForTurn)
967652
+ return;
967653
+ this.errorReportedForTurn = true;
967654
+ this.notify?.(`[TTS] Skipping part of the spoken response \u2014 synthesis kept failing (${summarizeError(error73)}). Playback continues with the rest.`);
967655
+ }
967536
967656
  reportError(error73) {
967537
967657
  if (this.errorReportedForTurn)
967538
967658
  return;
@@ -967540,6 +967660,7 @@ class SpokenTurnController {
967540
967660
  this.activeTurnId = null;
967541
967661
  this.chunker = null;
967542
967662
  this.stopTimer();
967663
+ this.resetPipeline();
967543
967664
  for (const controller of this.abortControllers)
967544
967665
  controller.abort();
967545
967666
  this.abortControllers.clear();
@@ -967548,17 +967669,35 @@ class SpokenTurnController {
967548
967669
  this.notify?.(`[TTS] Live playback stopped: ${summarizeError(error73)}`);
967549
967670
  }
967550
967671
  }
967672
+ function isTransientSynthesisError(error73) {
967673
+ const message = (error73 instanceof Error ? error73.message : String(error73)).toLowerCase();
967674
+ if (message.includes("429") || message.includes("rate limit") || message.includes("rate_limit") || message.includes("too many requests") || message.includes("concurrent"))
967675
+ return true;
967676
+ if (/http 5\d\d/.test(message))
967677
+ return true;
967678
+ return message.includes("fetch failed") || message.includes("network") || message.includes("timed out") || message.includes("timeout") || message.includes("econnreset") || message.includes("socket");
967679
+ }
967680
+ function findSplitIndex(text, limit3) {
967681
+ const space = text.lastIndexOf(" ", limit3);
967682
+ return space > 0 ? space : limit3;
967683
+ }
967551
967684
  function readOptionalConfigString2(configManager, key) {
967552
967685
  const value = String(configManager.get(key) ?? "").trim();
967553
967686
  return value || undefined;
967554
967687
  }
967688
+ function readOptionalConfigNumber(configManager, key) {
967689
+ const raw = configManager.get(key);
967690
+ const value = typeof raw === "number" ? raw : parseFloat(String(raw ?? ""));
967691
+ return isFinite(value) && value > 0 ? value : undefined;
967692
+ }
967555
967693
 
967556
967694
  // src/audio/spoken-turn-wiring.ts
967557
967695
  function wireSpokenTurnRuntime(options) {
967696
+ const player = options.playerFactory ? options.playerFactory() : new LocalStreamingAudioPlayer;
967558
967697
  const controller = new SpokenTurnController({
967559
967698
  voiceService: options.voiceService,
967560
967699
  configManager: options.configManager,
967561
- player: new LocalStreamingAudioPlayer,
967700
+ player,
967562
967701
  notify: options.notify
967563
967702
  });
967564
967703
  const turns = options.events.turns;
@@ -967574,7 +967713,35 @@ function wireSpokenTurnRuntime(options) {
967574
967713
  return {
967575
967714
  unsubs,
967576
967715
  submitNextTurn: (prompt) => controller.submitNextTurn(prompt),
967577
- stop: (message) => controller.stop(message)
967716
+ stop: (message) => controller.stop(message),
967717
+ stopForExit: (drainTimeoutMs) => controller.stopForExit(drainTimeoutMs)
967718
+ };
967719
+ }
967720
+
967721
+ // src/runtime/unhandled-rejection-guard.ts
967722
+ function createUnhandledRejectionHandler(deps) {
967723
+ let rejectionCount = 0;
967724
+ let windowStart = Date.now();
967725
+ return (reason) => {
967726
+ const now5 = Date.now();
967727
+ if (now5 - windowStart > 1e4) {
967728
+ rejectionCount = 0;
967729
+ windowStart = now5;
967730
+ }
967731
+ rejectionCount++;
967732
+ const msg = reason instanceof Error ? reason.message : String(reason);
967733
+ if (rejectionCount > 3) {
967734
+ logger.error("CRITICAL: cascading unhandled rejections \u2014 consider restarting", {
967735
+ count: rejectionCount,
967736
+ windowMs: now5 - windowStart,
967737
+ error: String(reason)
967738
+ });
967739
+ deps.notifyHigh(`[Critical] Multiple errors detected (${rejectionCount} in 10s). If the issue persists, please restart. Latest: ${msg}`);
967740
+ } else {
967741
+ deps.notifyHigh(`[Error] ${msg}`);
967742
+ logger.error("unhandledRejection", { error: String(reason) });
967743
+ }
967744
+ deps.render();
967578
967745
  };
967579
967746
  }
967580
967747
 
@@ -967711,8 +967878,8 @@ function formatDigestTime(at, from = Date.now()) {
967711
967878
  return `yesterday ${time4}`;
967712
967879
  return `${d4.toLocaleDateString(undefined, { month: "short", day: "numeric" })} ${time4}`;
967713
967880
  }
967714
- function formatRelativeTime(targetMs, nowMs3 = Date.now()) {
967715
- const diffMs = targetMs - nowMs3;
967881
+ function formatRelativeTime(targetMs, nowMs4 = Date.now()) {
967882
+ const diffMs = targetMs - nowMs4;
967716
967883
  if (diffMs <= 0)
967717
967884
  return "soon";
967718
967885
  const diffMin = Math.round(diffMs / 60000);
@@ -968104,36 +968271,26 @@ async function main() {
968104
968271
  let stopSpokenOutputForExit = null;
968105
968272
  let recoveryPending = false;
968106
968273
  const sigintHandler = () => input.feed("\x03");
968107
- let _unhandledRejectionCount = 0;
968108
- let _unhandledRejectionWindowStart = Date.now();
968109
- const unhandledRejectionHandler = (reason) => {
968110
- const now5 = Date.now();
968111
- if (now5 - _unhandledRejectionWindowStart > 1e4) {
968112
- _unhandledRejectionCount = 0;
968113
- _unhandledRejectionWindowStart = now5;
968114
- }
968115
- _unhandledRejectionCount++;
968116
- const msg = reason instanceof Error ? reason.message : String(reason);
968117
- if (_unhandledRejectionCount > 3) {
968118
- logger.error("CRITICAL: cascading unhandled rejections \u2014 consider restarting", {
968119
- count: _unhandledRejectionCount,
968120
- windowMs: now5 - _unhandledRejectionWindowStart,
968121
- error: String(reason)
968122
- });
968123
- systemMessageRouter.high(`[Critical] Multiple errors detected (${_unhandledRejectionCount} in 10s). If the issue persists, please restart. Latest: ${msg}`);
968124
- } else {
968125
- systemMessageRouter.high(`[Error] ${msg}`);
968126
- logger.error("unhandledRejection", { error: String(reason) });
968127
- }
968128
- render();
968129
- };
968274
+ const unhandledRejectionHandler = createUnhandledRejectionHandler({
968275
+ notifyHigh: (message) => systemMessageRouter.high(message),
968276
+ render: () => render()
968277
+ });
968130
968278
  const resizeHandler = () => {
968131
968279
  input.setContentWidth(getPromptContentWidth());
968132
968280
  compositor.resetDiff();
968133
968281
  render();
968134
968282
  };
968283
+ let exiting = false;
968135
968284
  const exitApp = () => {
968136
- stopSpokenOutputForExit?.();
968285
+ if (exiting)
968286
+ return;
968287
+ exiting = true;
968288
+ let spokenOutputDrain = Promise.resolve();
968289
+ try {
968290
+ spokenOutputDrain = Promise.resolve(stopSpokenOutputForExit?.()).then(() => {
968291
+ return;
968292
+ });
968293
+ } catch {}
968137
968294
  unsubs.forEach((fn) => fn());
968138
968295
  autonomy.stop();
968139
968296
  const snapshot2 = buildCurrentSessionSnapshot();
@@ -968153,7 +968310,9 @@ async function main() {
968153
968310
  allowTerminalWrite(() => stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE2 + MOUSE_DISABLE + FOCUS_DISABLE + CURSOR_SHOW + exitScreen));
968154
968311
  terminalOutputGuard.dispose();
968155
968312
  stdin.setRawMode(false);
968156
- process.exit(0);
968313
+ spokenOutputDrain.catch(() => {
968314
+ return;
968315
+ }).then(() => process.exit(0));
968157
968316
  };
968158
968317
  commandContext.exit = exitApp;
968159
968318
  const spokenTurns = wireSpokenTurnRuntime({
@@ -968165,7 +968324,7 @@ async function main() {
968165
968324
  render();
968166
968325
  }
968167
968326
  });
968168
- stopSpokenOutputForExit = () => spokenTurns.stop();
968327
+ stopSpokenOutputForExit = () => spokenTurns.stopForExit();
968169
968328
  unsubs.push(...spokenTurns.unsubs);
968170
968329
  unsubs.push(attachSpokenTurnModelRouting({
968171
968330
  orchestrator,