@askexenow/exe-os 0.9.69 → 0.9.71

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 (79) hide show
  1. package/deploy/stack-manifests/v0.9.json +96 -16
  2. package/dist/bin/agentic-ontology-backfill.js +33 -0
  3. package/dist/bin/agentic-reflection-backfill.js +33 -0
  4. package/dist/bin/agentic-semantic-label.js +33 -0
  5. package/dist/bin/backfill-conversations.js +33 -0
  6. package/dist/bin/backfill-responses.js +33 -0
  7. package/dist/bin/backfill-vectors.js +33 -0
  8. package/dist/bin/bulk-sync-postgres.js +33 -0
  9. package/dist/bin/cleanup-stale-review-tasks.js +33 -0
  10. package/dist/bin/cli.js +1284 -178
  11. package/dist/bin/exe-agent.js +6 -0
  12. package/dist/bin/exe-assign.js +33 -0
  13. package/dist/bin/exe-boot.js +33 -0
  14. package/dist/bin/exe-call.js +6 -0
  15. package/dist/bin/exe-cloud.js +33 -0
  16. package/dist/bin/exe-dispatch.js +33 -0
  17. package/dist/bin/exe-doctor.js +33 -0
  18. package/dist/bin/exe-export-behaviors.js +33 -0
  19. package/dist/bin/exe-forget.js +33 -0
  20. package/dist/bin/exe-gateway.js +178 -110
  21. package/dist/bin/exe-heartbeat.js +33 -0
  22. package/dist/bin/exe-kill.js +33 -0
  23. package/dist/bin/exe-launch-agent.js +33 -0
  24. package/dist/bin/exe-new-employee.js +6 -0
  25. package/dist/bin/exe-pending-messages.js +33 -0
  26. package/dist/bin/exe-pending-notifications.js +33 -0
  27. package/dist/bin/exe-pending-reviews.js +33 -0
  28. package/dist/bin/exe-rename.js +40 -4
  29. package/dist/bin/exe-review.js +33 -0
  30. package/dist/bin/exe-search.js +33 -0
  31. package/dist/bin/exe-session-cleanup.js +33 -0
  32. package/dist/bin/exe-start-codex.js +33 -0
  33. package/dist/bin/exe-start-opencode.js +33 -0
  34. package/dist/bin/exe-status.js +33 -0
  35. package/dist/bin/exe-team.js +33 -0
  36. package/dist/bin/git-sweep.js +33 -0
  37. package/dist/bin/graph-backfill.js +177 -110
  38. package/dist/bin/graph-export.js +33 -0
  39. package/dist/bin/intercom-check.js +33 -0
  40. package/dist/bin/registry-proxy.js +207 -0
  41. package/dist/bin/scan-tasks.js +33 -0
  42. package/dist/bin/setup.js +33 -0
  43. package/dist/bin/shard-migrate.js +33 -0
  44. package/dist/bin/stack-update.js +128 -0
  45. package/dist/gateway/index.js +178 -110
  46. package/dist/hooks/bug-report-worker.js +33 -0
  47. package/dist/hooks/codex-stop-task-finalizer.js +33 -0
  48. package/dist/hooks/commit-complete.js +33 -0
  49. package/dist/hooks/error-recall.js +33 -0
  50. package/dist/hooks/ingest.js +33 -0
  51. package/dist/hooks/instructions-loaded.js +33 -0
  52. package/dist/hooks/notification.js +33 -0
  53. package/dist/hooks/post-compact.js +33 -0
  54. package/dist/hooks/post-tool-combined.js +698 -17
  55. package/dist/hooks/pre-compact.js +33 -0
  56. package/dist/hooks/pre-tool-use.js +33 -0
  57. package/dist/hooks/prompt-submit.js +314 -0
  58. package/dist/hooks/session-end.js +33 -0
  59. package/dist/hooks/session-start.js +33 -0
  60. package/dist/hooks/stop.js +279 -12
  61. package/dist/hooks/subagent-stop.js +33 -0
  62. package/dist/hooks/summary-worker.js +33 -0
  63. package/dist/index.js +178 -110
  64. package/dist/lib/cloud-sync.js +27 -0
  65. package/dist/lib/database.js +27 -0
  66. package/dist/lib/db.js +27 -0
  67. package/dist/lib/device-registry.js +27 -0
  68. package/dist/lib/employee-templates.js +6 -0
  69. package/dist/lib/exe-daemon.js +639 -259
  70. package/dist/lib/hybrid-search.js +33 -0
  71. package/dist/lib/registry-proxy.js +162 -0
  72. package/dist/lib/schedules.js +33 -0
  73. package/dist/lib/store.js +33 -0
  74. package/dist/mcp/server.js +561 -244
  75. package/dist/runtime/index.js +33 -0
  76. package/dist/tui/App.js +33 -0
  77. package/package.json +3 -2
  78. package/stack.release.json +6 -4
  79. package/stack.release.schema.json +89 -18
@@ -2222,6 +2222,33 @@ async function ensureSchema() {
2222
2222
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
2223
2223
  ON chat_history(session_id, id);
2224
2224
  `);
2225
+ await client.executeMultiple(`
2226
+ CREATE TABLE IF NOT EXISTS session_events (
2227
+ id TEXT PRIMARY KEY,
2228
+ agent_id TEXT NOT NULL,
2229
+ agent_role TEXT NOT NULL,
2230
+ session_id TEXT NOT NULL,
2231
+ session_scope TEXT,
2232
+ project_name TEXT NOT NULL,
2233
+ event_index INTEGER NOT NULL,
2234
+ event_type TEXT NOT NULL,
2235
+ tool_name TEXT,
2236
+ tool_use_id TEXT,
2237
+ content TEXT NOT NULL,
2238
+ payload_json TEXT,
2239
+ has_error INTEGER NOT NULL DEFAULT 0,
2240
+ created_at TEXT NOT NULL
2241
+ );
2242
+
2243
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
2244
+ ON session_events(agent_id, created_at DESC);
2245
+
2246
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
2247
+ ON session_events(session_id, event_index);
2248
+
2249
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
2250
+ ON session_events(session_scope, agent_id, created_at DESC);
2251
+ `);
2225
2252
  await client.executeMultiple(`
2226
2253
  CREATE TABLE IF NOT EXISTS workspaces (
2227
2254
  id TEXT PRIMARY KEY,
@@ -3353,6 +3380,12 @@ var init_platform_procedures = __esm({
3353
3380
  priority: "p0",
3354
3381
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
3355
3382
  },
3383
+ {
3384
+ title: "Code context first for repository orientation",
3385
+ domain: "workflow",
3386
+ priority: "p1",
3387
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
3388
+ },
3356
3389
  {
3357
3390
  title: "Commit discipline \u2014 never leave verified work floating",
3358
3391
  domain: "workflow",
@@ -4662,13 +4695,75 @@ import crypto2 from "crypto";
4662
4695
 
4663
4696
  // src/lib/code-chunker.ts
4664
4697
  import ts from "typescript";
4698
+ var LANGUAGE_BY_EXTENSION = {
4699
+ c: "c",
4700
+ cc: "cpp",
4701
+ cpp: "cpp",
4702
+ cxx: "cpp",
4703
+ h: "c",
4704
+ hh: "cpp",
4705
+ hpp: "cpp",
4706
+ cs: "csharp",
4707
+ css: "css",
4708
+ scss: "scss",
4709
+ f: "fortran",
4710
+ f90: "fortran",
4711
+ f95: "fortran",
4712
+ go: "go",
4713
+ html: "html",
4714
+ htm: "html",
4715
+ java: "java",
4716
+ js: "javascript",
4717
+ jsx: "javascript",
4718
+ json: "json",
4719
+ kt: "kotlin",
4720
+ kts: "kotlin",
4721
+ lua: "lua",
4722
+ md: "markdown",
4723
+ mdx: "mdx",
4724
+ pas: "pascal",
4725
+ php: "php",
4726
+ py: "python",
4727
+ r: "r",
4728
+ rb: "ruby",
4729
+ rs: "rust",
4730
+ scala: "scala",
4731
+ sc: "scala",
4732
+ sol: "solidity",
4733
+ sql: "sql",
4734
+ svelte: "svelte",
4735
+ swift: "swift",
4736
+ toml: "toml",
4737
+ ts: "typescript",
4738
+ tsx: "typescript",
4739
+ vue: "vue",
4740
+ xml: "xml",
4741
+ yaml: "yaml",
4742
+ yml: "yaml",
4743
+ sh: "shell",
4744
+ bash: "shell",
4745
+ zsh: "shell"
4746
+ };
4747
+ var TEXT_LIKE_LANGUAGES = /* @__PURE__ */ new Set(["json", "markdown", "mdx", "toml", "yaml", "xml", "html", "css", "scss", "sql"]);
4748
+ function languageForFile(filePath) {
4749
+ const base = filePath.split(/[\\/]/).pop()?.toLowerCase() ?? "";
4750
+ if (["dockerfile", "makefile", "rakefile", "gemfile"].includes(base)) return base;
4751
+ const ext = base.includes(".") ? base.split(".").pop() : void 0;
4752
+ return ext ? LANGUAGE_BY_EXTENSION[ext] : void 0;
4753
+ }
4665
4754
  function chunkSourceFile(source, fileName = "file.ts") {
4755
+ const language = languageForFile(fileName);
4756
+ if (language === "typescript" || language === "javascript") {
4757
+ return chunkTypeScriptLike(source, fileName);
4758
+ }
4759
+ return chunkGenericSource(source, fileName, language);
4760
+ }
4761
+ function chunkTypeScriptLike(source, fileName) {
4666
4762
  const sourceFile = ts.createSourceFile(
4667
4763
  fileName,
4668
4764
  source,
4669
4765
  ts.ScriptTarget.Latest,
4670
4766
  true,
4671
- // setParentNodes
4672
4767
  fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
4673
4768
  );
4674
4769
  const chunks = [];
@@ -4690,149 +4785,121 @@ function chunkSourceFile(source, fileName = "file.ts") {
4690
4785
  if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
4691
4786
  return node.name?.getText(sourceFile) ?? "(anonymous)";
4692
4787
  }
4693
- if (ts.isClassDeclaration(node)) {
4694
- return node.name?.getText(sourceFile) ?? "(anonymous class)";
4695
- }
4696
- if (ts.isInterfaceDeclaration(node)) {
4697
- return node.name.getText(sourceFile);
4698
- }
4699
- if (ts.isTypeAliasDeclaration(node)) {
4700
- return node.name.getText(sourceFile);
4701
- }
4702
- if (ts.isEnumDeclaration(node)) {
4703
- return node.name.getText(sourceFile);
4704
- }
4705
- if (ts.isVariableStatement(node)) {
4706
- const decls = node.declarationList.declarations;
4707
- return decls.map((d) => d.name.getText(sourceFile)).join(", ");
4708
- }
4709
- if (ts.isExportAssignment(node)) {
4710
- return "default export";
4711
- }
4788
+ if (ts.isClassDeclaration(node)) return node.name?.getText(sourceFile) ?? "(anonymous class)";
4789
+ if (ts.isInterfaceDeclaration(node)) return node.name.getText(sourceFile);
4790
+ if (ts.isTypeAliasDeclaration(node)) return node.name.getText(sourceFile);
4791
+ if (ts.isEnumDeclaration(node)) return node.name.getText(sourceFile);
4792
+ if (ts.isVariableStatement(node)) return node.declarationList.declarations.map((d) => d.name.getText(sourceFile)).join(", ");
4793
+ if (ts.isExportAssignment(node)) return "default export";
4712
4794
  return "(unknown)";
4713
4795
  }
4714
4796
  function visitTopLevel(node) {
4715
4797
  if (ts.isImportDeclaration(node)) {
4716
- importLines.push({
4717
- start: getLineNumber(node.getStart(sourceFile)),
4718
- end: getLineNumber(node.getEnd())
4719
- });
4798
+ importLines.push({ start: getLineNumber(node.getStart(sourceFile)), end: getLineNumber(node.getEnd()) });
4720
4799
  return;
4721
4800
  }
4722
4801
  if (ts.isFunctionDeclaration(node)) {
4723
- chunks.push({
4724
- kind: "function",
4725
- name: getName(node),
4726
- text: getNodeText(node),
4727
- startLine: getLineNumber(node.getStart(sourceFile)),
4728
- endLine: getLineNumber(node.getEnd()),
4729
- comment: getLeadingComment(node)
4730
- });
4802
+ chunks.push({ kind: "function", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
4731
4803
  return;
4732
4804
  }
4733
4805
  if (ts.isClassDeclaration(node)) {
4734
- chunks.push({
4735
- kind: "class",
4736
- name: getName(node),
4737
- text: getNodeText(node),
4738
- startLine: getLineNumber(node.getStart(sourceFile)),
4739
- endLine: getLineNumber(node.getEnd()),
4740
- comment: getLeadingComment(node)
4741
- });
4806
+ chunks.push({ kind: "class", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
4742
4807
  return;
4743
4808
  }
4744
- if (ts.isInterfaceDeclaration(node)) {
4745
- chunks.push({
4746
- kind: "type",
4747
- name: getName(node),
4748
- text: getNodeText(node),
4749
- startLine: getLineNumber(node.getStart(sourceFile)),
4750
- endLine: getLineNumber(node.getEnd()),
4751
- comment: getLeadingComment(node)
4752
- });
4753
- return;
4754
- }
4755
- if (ts.isTypeAliasDeclaration(node)) {
4756
- chunks.push({
4757
- kind: "type",
4758
- name: getName(node),
4759
- text: getNodeText(node),
4760
- startLine: getLineNumber(node.getStart(sourceFile)),
4761
- endLine: getLineNumber(node.getEnd()),
4762
- comment: getLeadingComment(node)
4763
- });
4764
- return;
4765
- }
4766
- if (ts.isEnumDeclaration(node)) {
4767
- chunks.push({
4768
- kind: "type",
4769
- name: getName(node),
4770
- text: getNodeText(node),
4771
- startLine: getLineNumber(node.getStart(sourceFile)),
4772
- endLine: getLineNumber(node.getEnd()),
4773
- comment: getLeadingComment(node)
4774
- });
4809
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
4810
+ chunks.push({ kind: "type", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
4775
4811
  return;
4776
4812
  }
4777
4813
  if (ts.isVariableStatement(node)) {
4778
- const decls = node.declarationList.declarations;
4779
- const isFnLike = decls.some(
4780
- (d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))
4781
- );
4782
- chunks.push({
4783
- kind: isFnLike ? "function" : "variable",
4784
- name: getName(node),
4785
- text: getNodeText(node),
4786
- startLine: getLineNumber(node.getStart(sourceFile)),
4787
- endLine: getLineNumber(node.getEnd()),
4788
- comment: getLeadingComment(node)
4789
- });
4814
+ const isFnLike = node.declarationList.declarations.some((d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)));
4815
+ chunks.push({ kind: isFnLike ? "function" : "variable", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
4790
4816
  return;
4791
4817
  }
4792
4818
  if (ts.isExportAssignment(node)) {
4793
- chunks.push({
4794
- kind: "export",
4795
- name: "default export",
4796
- text: getNodeText(node),
4797
- startLine: getLineNumber(node.getStart(sourceFile)),
4798
- endLine: getLineNumber(node.getEnd()),
4799
- comment: getLeadingComment(node)
4800
- });
4819
+ chunks.push({ kind: "export", name: "default export", text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
4801
4820
  return;
4802
4821
  }
4803
4822
  if (ts.isExpressionStatement(node)) {
4804
4823
  const text = getNodeText(node);
4805
- if (text.length > 10) {
4806
- chunks.push({
4807
- kind: "other",
4808
- name: text.slice(0, 40).replace(/\n/g, " "),
4809
- text,
4810
- startLine: getLineNumber(node.getStart(sourceFile)),
4811
- endLine: getLineNumber(node.getEnd())
4812
- });
4813
- }
4814
- return;
4824
+ if (text.length > 10) chunks.push({ kind: "other", name: text.slice(0, 40).replace(/\n/g, " "), text, startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()) });
4815
4825
  }
4816
4826
  }
4817
4827
  sourceFile.statements.forEach(visitTopLevel);
4818
4828
  if (importLines.length > 0) {
4819
4829
  const startLine = importLines[0].start;
4820
4830
  const endLine = importLines[importLines.length - 1].end;
4821
- const importText = lines.slice(startLine - 1, endLine).join("\n");
4822
- chunks.unshift({
4823
- kind: "import",
4824
- name: `${importLines.length} imports`,
4825
- text: importText,
4826
- startLine,
4831
+ chunks.unshift({ kind: "import", name: `${importLines.length} imports`, text: lines.slice(startLine - 1, endLine).join("\n"), startLine, endLine });
4832
+ }
4833
+ chunks.sort((a, b) => a.startLine - b.startLine);
4834
+ return chunks.length > 0 ? chunks : chunkByWindows(source, 80);
4835
+ }
4836
+ function chunkGenericSource(source, _fileName, language) {
4837
+ const lines = source.split("\n");
4838
+ if (source.trim().length === 0) return [];
4839
+ if (language && TEXT_LIKE_LANGUAGES.has(language)) return chunkTextLike(lines, language);
4840
+ const chunks = [];
4841
+ const patterns = [
4842
+ { kind: "function", regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
4843
+ { kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
4844
+ { kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*[<(]/, name: (m) => m[1] },
4845
+ { kind: "class", regex: /^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
4846
+ { kind: "function", regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
4847
+ { kind: "function", regex: /^\s*(?:public|private|protected|static|final|suspend|fun|override|open|internal|export|async|func|function|subroutine)\s+.*?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
4848
+ { kind: "function", regex: /^\s*function\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
4849
+ { kind: "type", regex: /^\s*(?:interface|type|enum|record|data\s+class|case\s+class|contract|library)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
4850
+ { kind: "section", regex: /^\s{0,3}#{1,6}\s+(.+)$/, name: (m) => m[1].trim() }
4851
+ ];
4852
+ const starts = [];
4853
+ for (let i = 0; i < lines.length; i++) {
4854
+ const line = lines[i];
4855
+ for (const pattern of patterns) {
4856
+ const match = line.match(pattern.regex);
4857
+ if (match) {
4858
+ starts.push({ line: i + 1, kind: pattern.kind, name: pattern.name(match) });
4859
+ break;
4860
+ }
4861
+ }
4862
+ }
4863
+ if (starts.length === 0) return chunkByWindows(source, 80);
4864
+ for (let i = 0; i < starts.length; i++) {
4865
+ const start = starts[i];
4866
+ const next = starts[i + 1]?.line ?? lines.length + 1;
4867
+ const endLine = Math.max(start.line, next - 1);
4868
+ chunks.push({
4869
+ kind: start.kind,
4870
+ name: start.name,
4871
+ text: lines.slice(start.line - 1, endLine).join("\n"),
4872
+ startLine: start.line,
4827
4873
  endLine
4828
4874
  });
4829
4875
  }
4830
- chunks.sort((a, b) => a.startLine - b.startLine);
4876
+ return chunks;
4877
+ }
4878
+ function chunkTextLike(lines, language) {
4879
+ if (language === "markdown" || language === "mdx") {
4880
+ const starts = lines.map((line, i) => ({ line, i })).filter(({ line }) => /^\s{0,3}#{1,6}\s+/.test(line));
4881
+ if (starts.length > 0) {
4882
+ return starts.map((start, idx) => {
4883
+ const end = starts[idx + 1]?.i ?? lines.length;
4884
+ const title = start.line.replace(/^\s{0,3}#{1,6}\s+/, "").trim();
4885
+ return { kind: "section", name: title, text: lines.slice(start.i, end).join("\n"), startLine: start.i + 1, endLine: end };
4886
+ });
4887
+ }
4888
+ }
4889
+ return chunkByWindows(lines.join("\n"), 80);
4890
+ }
4891
+ function chunkByWindows(source, windowLines) {
4892
+ const lines = source.split("\n");
4893
+ const chunks = [];
4894
+ for (let i = 0; i < lines.length; i += windowLines) {
4895
+ const end = Math.min(lines.length, i + windowLines);
4896
+ const text = lines.slice(i, end).join("\n");
4897
+ if (text.trim()) chunks.push({ kind: "other", name: `lines ${i + 1}-${end}`, text, startLine: i + 1, endLine: end });
4898
+ }
4831
4899
  return chunks;
4832
4900
  }
4833
4901
  function isChunkable(filePath) {
4834
- const ext = filePath.split(".").pop()?.toLowerCase();
4835
- return ext === "ts" || ext === "tsx" || ext === "js" || ext === "jsx";
4902
+ return Boolean(languageForFile(filePath));
4836
4903
  }
4837
4904
 
4838
4905
  // src/lib/graph-rag.ts
@@ -2437,6 +2437,33 @@ async function ensureSchema() {
2437
2437
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
2438
2438
  ON chat_history(session_id, id);
2439
2439
  `);
2440
+ await client.executeMultiple(`
2441
+ CREATE TABLE IF NOT EXISTS session_events (
2442
+ id TEXT PRIMARY KEY,
2443
+ agent_id TEXT NOT NULL,
2444
+ agent_role TEXT NOT NULL,
2445
+ session_id TEXT NOT NULL,
2446
+ session_scope TEXT,
2447
+ project_name TEXT NOT NULL,
2448
+ event_index INTEGER NOT NULL,
2449
+ event_type TEXT NOT NULL,
2450
+ tool_name TEXT,
2451
+ tool_use_id TEXT,
2452
+ content TEXT NOT NULL,
2453
+ payload_json TEXT,
2454
+ has_error INTEGER NOT NULL DEFAULT 0,
2455
+ created_at TEXT NOT NULL
2456
+ );
2457
+
2458
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
2459
+ ON session_events(agent_id, created_at DESC);
2460
+
2461
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
2462
+ ON session_events(session_id, event_index);
2463
+
2464
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
2465
+ ON session_events(session_scope, agent_id, created_at DESC);
2466
+ `);
2440
2467
  await client.executeMultiple(`
2441
2468
  CREATE TABLE IF NOT EXISTS workspaces (
2442
2469
  id TEXT PRIMARY KEY,
@@ -4125,6 +4152,12 @@ var init_platform_procedures = __esm({
4125
4152
  priority: "p0",
4126
4153
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
4127
4154
  },
4155
+ {
4156
+ title: "Code context first for repository orientation",
4157
+ domain: "workflow",
4158
+ priority: "p1",
4159
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
4160
+ },
4128
4161
  {
4129
4162
  title: "Commit discipline \u2014 never leave verified work floating",
4130
4163
  domain: "workflow",
@@ -2546,6 +2546,33 @@ async function ensureSchema() {
2546
2546
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
2547
2547
  ON chat_history(session_id, id);
2548
2548
  `);
2549
+ await client.executeMultiple(`
2550
+ CREATE TABLE IF NOT EXISTS session_events (
2551
+ id TEXT PRIMARY KEY,
2552
+ agent_id TEXT NOT NULL,
2553
+ agent_role TEXT NOT NULL,
2554
+ session_id TEXT NOT NULL,
2555
+ session_scope TEXT,
2556
+ project_name TEXT NOT NULL,
2557
+ event_index INTEGER NOT NULL,
2558
+ event_type TEXT NOT NULL,
2559
+ tool_name TEXT,
2560
+ tool_use_id TEXT,
2561
+ content TEXT NOT NULL,
2562
+ payload_json TEXT,
2563
+ has_error INTEGER NOT NULL DEFAULT 0,
2564
+ created_at TEXT NOT NULL
2565
+ );
2566
+
2567
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
2568
+ ON session_events(agent_id, created_at DESC);
2569
+
2570
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
2571
+ ON session_events(session_id, event_index);
2572
+
2573
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
2574
+ ON session_events(session_scope, agent_id, created_at DESC);
2575
+ `);
2549
2576
  await client.executeMultiple(`
2550
2577
  CREATE TABLE IF NOT EXISTS workspaces (
2551
2578
  id TEXT PRIMARY KEY,
@@ -4234,6 +4261,12 @@ var init_platform_procedures = __esm({
4234
4261
  priority: "p0",
4235
4262
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
4236
4263
  },
4264
+ {
4265
+ title: "Code context first for repository orientation",
4266
+ domain: "workflow",
4267
+ priority: "p1",
4268
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
4269
+ },
4237
4270
  {
4238
4271
  title: "Commit discipline \u2014 never leave verified work floating",
4239
4272
  domain: "workflow",
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/is-main.ts
4
+ import { realpathSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ function isMainModule(importMetaUrl) {
7
+ if (process.argv[1] == null) return false;
8
+ if (process.argv[1].includes("mcp/server")) return false;
9
+ try {
10
+ const scriptPath = realpathSync(process.argv[1]);
11
+ const modulePath = realpathSync(fileURLToPath(importMetaUrl));
12
+ return scriptPath === modulePath;
13
+ } catch {
14
+ return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
15
+ }
16
+ }
17
+
18
+ // src/lib/registry-proxy.ts
19
+ import { createServer } from "http";
20
+ import { Readable } from "stream";
21
+ function parsePullTokens(raw) {
22
+ return (raw ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
23
+ }
24
+ function registryProxyOptionsFromEnv(env = process.env) {
25
+ const upstreamToken = env.EXE_REGISTRY_PROXY_UPSTREAM_TOKEN || env.GHCR_TOKEN || "";
26
+ const pullTokens = parsePullTokens(env.EXE_REGISTRY_PROXY_PULL_TOKENS);
27
+ return {
28
+ port: Number(env.EXE_REGISTRY_PROXY_PORT || 3201),
29
+ host: env.EXE_REGISTRY_PROXY_HOST || "0.0.0.0",
30
+ upstream: env.EXE_REGISTRY_PROXY_UPSTREAM || "https://ghcr.io",
31
+ upstreamUsername: env.EXE_REGISTRY_PROXY_UPSTREAM_USERNAME || env.GHCR_USERNAME || "askexe",
32
+ upstreamToken,
33
+ pullTokens,
34
+ allowedNamespace: env.EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE || "askexe"
35
+ };
36
+ }
37
+ function assertRegistryProxyConfig(options) {
38
+ if (!options.upstreamToken) throw new Error("EXE_REGISTRY_PROXY_UPSTREAM_TOKEN or GHCR_TOKEN is required");
39
+ if (options.pullTokens.length === 0) throw new Error("EXE_REGISTRY_PROXY_PULL_TOKENS is required");
40
+ if (!options.allowedNamespace || !/^[a-z0-9._-]+$/i.test(options.allowedNamespace)) {
41
+ throw new Error("EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE must be a registry-safe namespace");
42
+ }
43
+ }
44
+ function timingSafeIncludes(values, candidate) {
45
+ return values.some((value) => value === candidate);
46
+ }
47
+ function parseBasicAuth(header) {
48
+ if (!header?.startsWith("Basic ")) return null;
49
+ try {
50
+ const decoded = Buffer.from(header.slice("Basic ".length), "base64").toString("utf8");
51
+ const idx = decoded.indexOf(":");
52
+ if (idx < 0) return null;
53
+ return { username: decoded.slice(0, idx), password: decoded.slice(idx + 1) };
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ function unauthorized(res) {
59
+ res.writeHead(401, {
60
+ "www-authenticate": 'Basic realm="AskExe Registry Proxy"',
61
+ "content-type": "application/json"
62
+ });
63
+ res.end(JSON.stringify({ error: "registry proxy authentication required" }));
64
+ }
65
+ function isAllowedPath(pathname, namespace) {
66
+ if (pathname === "/health") return true;
67
+ if (pathname === "/v2/" || pathname === "/v2") return true;
68
+ const prefix = `/v2/${namespace}/`;
69
+ if (!pathname.startsWith(prefix)) return false;
70
+ if (pathname.includes("..") || /%2f/i.test(pathname)) return false;
71
+ return true;
72
+ }
73
+ function parseBearerChallenge(header) {
74
+ if (!header?.toLowerCase().startsWith("bearer ")) return null;
75
+ const raw = header.slice("Bearer ".length);
76
+ const params = new URLSearchParams();
77
+ for (const part of raw.match(/(?:[^,"]+|"[^"]*")+/g) ?? []) {
78
+ const idx = part.indexOf("=");
79
+ if (idx < 0) continue;
80
+ const key = part.slice(0, idx).trim();
81
+ const value = part.slice(idx + 1).trim().replace(/^"|"$/g, "");
82
+ params.set(key, value);
83
+ }
84
+ const realm = params.get("realm");
85
+ if (!realm) return null;
86
+ params.delete("realm");
87
+ return { realm, params };
88
+ }
89
+ async function fetchUpstreamWithRegistryAuth(url, init, upstreamBasicAuth) {
90
+ const firstHeaders = new Headers(init.headers);
91
+ firstHeaders.set("authorization", `Basic ${upstreamBasicAuth}`);
92
+ const first = await fetch(url, { ...init, headers: firstHeaders });
93
+ if (first.status !== 401) return first;
94
+ const challenge = parseBearerChallenge(first.headers.get("www-authenticate"));
95
+ if (!challenge) return first;
96
+ const tokenUrl = new URL(challenge.realm);
97
+ for (const [key, value] of challenge.params.entries()) tokenUrl.searchParams.set(key, value);
98
+ const tokenRes = await fetch(tokenUrl, { headers: { authorization: `Basic ${upstreamBasicAuth}` } });
99
+ if (!tokenRes.ok) return first;
100
+ const tokenJson = await tokenRes.json();
101
+ const token = tokenJson.token ?? tokenJson.access_token;
102
+ if (!token) return first;
103
+ const retryHeaders = new Headers(init.headers);
104
+ retryHeaders.set("authorization", `Bearer ${token}`);
105
+ return fetch(url, { ...init, headers: retryHeaders });
106
+ }
107
+ function copyResponseHeaders(from, to) {
108
+ for (const [key, value] of from.entries()) {
109
+ const lower = key.toLowerCase();
110
+ if (["connection", "keep-alive", "transfer-encoding", "content-encoding"].includes(lower)) continue;
111
+ to.setHeader(key, value);
112
+ }
113
+ }
114
+ function createRegistryProxyServer(options) {
115
+ assertRegistryProxyConfig(options);
116
+ const upstream = new URL(options.upstream ?? "https://ghcr.io");
117
+ const namespace = options.allowedNamespace ?? "askexe";
118
+ const upstreamAuth = Buffer.from(`${options.upstreamUsername ?? "askexe"}:${options.upstreamToken}`).toString("base64");
119
+ return createServer(async (req, res) => {
120
+ const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
121
+ if (requestUrl.pathname === "/health") {
122
+ res.writeHead(200, { "content-type": "application/json" });
123
+ res.end(JSON.stringify({ ok: true, service: "exe-registry-proxy", upstream: upstream.origin, namespace }));
124
+ return;
125
+ }
126
+ if (!isAllowedPath(requestUrl.pathname, namespace)) {
127
+ res.writeHead(404, { "content-type": "application/json" });
128
+ res.end(JSON.stringify({ error: "registry path not allowed" }));
129
+ return;
130
+ }
131
+ const auth = parseBasicAuth(req.headers.authorization);
132
+ if (!auth || !timingSafeIncludes(options.pullTokens, auth.password)) {
133
+ unauthorized(res);
134
+ return;
135
+ }
136
+ const upstreamUrl = new URL(requestUrl.pathname + requestUrl.search, upstream.origin);
137
+ const headers = new Headers();
138
+ for (const [key, value] of Object.entries(req.headers)) {
139
+ if (!value) continue;
140
+ const lower = key.toLowerCase();
141
+ if (["host", "connection", "authorization", "content-length"].includes(lower)) continue;
142
+ headers.set(key, Array.isArray(value) ? value.join(",") : value);
143
+ }
144
+ headers.set("authorization", `Basic ${upstreamAuth}`);
145
+ try {
146
+ const upstreamRes = await fetchUpstreamWithRegistryAuth(upstreamUrl, {
147
+ method: req.method,
148
+ headers,
149
+ body: req.method === "GET" || req.method === "HEAD" ? void 0 : req,
150
+ // Required by undici when streaming request bodies.
151
+ duplex: "half"
152
+ }, upstreamAuth);
153
+ res.statusCode = upstreamRes.status;
154
+ res.statusMessage = upstreamRes.statusText;
155
+ copyResponseHeaders(upstreamRes.headers, res);
156
+ if (!upstreamRes.body || req.method === "HEAD") {
157
+ res.end();
158
+ return;
159
+ }
160
+ Readable.fromWeb(upstreamRes.body).pipe(res);
161
+ } catch (err) {
162
+ res.writeHead(502, { "content-type": "application/json" });
163
+ res.end(JSON.stringify({ error: "upstream registry proxy failed", detail: err instanceof Error ? err.message : String(err) }));
164
+ }
165
+ });
166
+ }
167
+ async function runRegistryProxy(options = registryProxyOptionsFromEnv()) {
168
+ const server = createRegistryProxyServer(options);
169
+ await new Promise((resolve) => server.listen(options.port, options.host, resolve));
170
+ console.log(`exe-registry-proxy listening on ${options.host ?? "0.0.0.0"}:${options.port}`);
171
+ console.log(`proxying /v2/${options.allowedNamespace ?? "askexe"}/* -> ${options.upstream ?? "https://ghcr.io"}`);
172
+ }
173
+
174
+ // src/bin/registry-proxy.ts
175
+ function printHelp() {
176
+ console.log(`exe-os registry-proxy \u2014 authenticated pull-through proxy for AskExe customer images
177
+
178
+ Environment:
179
+ EXE_REGISTRY_PROXY_PORT=3201
180
+ EXE_REGISTRY_PROXY_HOST=0.0.0.0
181
+ EXE_REGISTRY_PROXY_UPSTREAM=https://ghcr.io
182
+ EXE_REGISTRY_PROXY_UPSTREAM_USERNAME=<github-user>
183
+ EXE_REGISTRY_PROXY_UPSTREAM_TOKEN=<askexe-ghcr-read-token>
184
+ EXE_REGISTRY_PROXY_PULL_TOKENS=<customer-token-1,customer-token-2>
185
+ EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE=askexe
186
+
187
+ Docker image shape for clients:
188
+ registry.askexe.com/askexe/exed:v0.9.3
189
+ registry.askexe.com/askexe/exe-crm:v0.9.3
190
+ `);
191
+ }
192
+ async function main(args = process.argv.slice(2)) {
193
+ if (args.includes("--help") || args.includes("-h")) {
194
+ printHelp();
195
+ return;
196
+ }
197
+ await runRegistryProxy(registryProxyOptionsFromEnv());
198
+ }
199
+ if (isMainModule(import.meta.url)) {
200
+ main().catch((err) => {
201
+ console.error(err instanceof Error ? err.message : String(err));
202
+ process.exit(1);
203
+ });
204
+ }
205
+ export {
206
+ main
207
+ };