indusagi-coding-agent 0.2.12 → 0.2.14

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 (81) hide show
  1. package/.env.review.example +25 -0
  2. package/README.md +30 -0
  3. package/REVIEW_AGENT.md +137 -0
  4. package/dist/entry.js +1861 -979
  5. package/dist/guardrails.js +212 -36
  6. package/dist/index.js +2829 -1057
  7. package/dist/review-agent.js +4461 -0
  8. package/dist/review-server.js +4633 -0
  9. package/package.json +24 -7
  10. package/src/boot/auth-vault.ts +4 -5
  11. package/src/boot/contract.ts +1 -1
  12. package/src/boot/runners/addon-wiring.ts +111 -3
  13. package/src/boot/runners/benchmark-integrity.ts +107 -0
  14. package/src/boot/runners/repl-runner.ts +2 -1
  15. package/src/boot/runners/session.ts +26 -12
  16. package/src/boot/server-token.ts +20 -11
  17. package/src/boot/stages.ts +3 -6
  18. package/src/briefing/context-docs.ts +144 -12
  19. package/src/channels/oneshot.ts +6 -0
  20. package/src/conductor/catalog/catalog.ts +3 -1
  21. package/src/conductor/conductor.ts +77 -17
  22. package/src/conductor/contract.ts +9 -7
  23. package/src/conductor/index.ts +1 -1
  24. package/src/conductor/transcript-store/store.ts +4 -8
  25. package/src/console/auth-status.ts +9 -4
  26. package/src/console/components/AgentsView.ts +2 -2
  27. package/src/console/components/BackgroundAgents.ts +15 -2
  28. package/src/console/components/Banner.ts +20 -9
  29. package/src/console/components/Composer.ts +57 -82
  30. package/src/console/components/ScrollViewport.ts +46 -0
  31. package/src/console/components/TerminalConsole.ts +165 -162
  32. package/src/console/components/WorkingIndicator.ts +6 -5
  33. package/src/console/components/banner-sweep.ts +5 -1
  34. package/src/console/components/welcome.ts +3 -3
  35. package/src/console/contract.ts +10 -5
  36. package/src/console/index.ts +7 -7
  37. package/src/console/input/complete.ts +2 -2
  38. package/src/console/input/dir-reader.ts +3 -1
  39. package/src/console/input/graphemes.ts +22 -0
  40. package/src/console/input/keymap.ts +6 -4
  41. package/src/console/mount.ts +45 -3
  42. package/src/console/overlays/auth.ts +28 -8
  43. package/src/console/overlays/boards.ts +13 -40
  44. package/src/console/overlays/host.ts +10 -0
  45. package/src/console/overlays/pickers.ts +35 -30
  46. package/src/console/reducer.ts +12 -5
  47. package/src/console/slash/builtins.ts +4 -3
  48. package/src/console/slash/commands/integrations.ts +13 -14
  49. package/src/console/slash/commands/transcript.ts +3 -8
  50. package/src/console/slash/commands/workbench.ts +4 -4
  51. package/src/console/test-support.ts +48 -0
  52. package/src/index.ts +1 -0
  53. package/src/integrations/sarvam/config.ts +15 -13
  54. package/src/kit/external-editor.ts +17 -1
  55. package/src/kit/run-shell.ts +31 -0
  56. package/src/launch/catalog.ts +4 -2
  57. package/src/launch/contract.ts +1 -0
  58. package/src/launch/invocation/flags.ts +13 -1
  59. package/src/launch/invocation/read.ts +4 -3
  60. package/src/launch/login.ts +23 -10
  61. package/src/launch/oauth.ts +3 -0
  62. package/src/models.ts +166 -0
  63. package/src/review-agent/config.ts +75 -0
  64. package/src/review-agent/contract.ts +59 -0
  65. package/src/review-agent/github.ts +242 -0
  66. package/src/review-agent/index.ts +8 -0
  67. package/src/review-agent/prompt.ts +132 -0
  68. package/src/review-agent/reviewer.ts +118 -0
  69. package/src/review-agent/server.ts +221 -0
  70. package/src/review-agent/service.ts +165 -0
  71. package/src/review-agent/supabase.ts +209 -0
  72. package/src/review-agent/webhook.ts +76 -0
  73. package/src/security/private-state.ts +122 -0
  74. package/src/window-budget/summarize/condense.ts +4 -1
  75. package/src/workspace/brand.ts +1 -1
  76. package/src/workspace/locator.ts +2 -2
  77. package/supabase/migrations/20260827031803_indusagi_review_agent.sql +109 -0
  78. package/supabase/migrations/20260827035747_atomic_webhook_enqueue.sql +75 -0
  79. package/supabase/migrations/20260827040104_check_run_reconciliation.sql +8 -0
  80. package/supabase/migrations/20260827041049_owned_job_leases.sql +80 -0
  81. package/supabase/migrations/20260827042500_static_token_webhooks.sql +75 -0
@@ -812,49 +812,52 @@ function composeBriefing(input) {
812
812
  }
813
813
 
814
814
  // src/briefing/context-docs.ts
815
- import { existsSync, readFileSync, realpathSync } from "node:fs";
816
- import { dirname, extname, isAbsolute, join, parse, resolve } from "node:path";
815
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync } from "node:fs";
816
+ import { basename, dirname, extname, isAbsolute, join, parse, resolve, sep } from "node:path";
817
817
  var DEFAULT_MAX_IMPORT_DEPTH = 5;
818
818
  var DEFAULT_MAX_BYTES_PER_DOC = 4e4;
819
819
  var CANDIDATE_FILES = ["AGENTS.md", "CLAUDE.md", "INDUSAGI.md", "CLAUDE.local.md", "AGENTS.local.md", join(".indusagi", "CLAUDE.md"), join(".claude", "CLAUDE.md")];
820
- var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".json", ".jsonc", ".yaml", ".yml", ".toml", ".xml", ".csv", ".html", ".htm", ".css", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".py", ".rb", ".go", ".rs", ".sh", ".env"]);
820
+ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".json", ".jsonc", ".yaml", ".yml", ".toml", ".xml", ".csv", ".html", ".htm", ".css", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".py", ".rb", ".go", ".rs", ".sh"]);
821
821
  var IMPORT_REGEX = /(?:^|\s)@((?:[^\s\\]|\\ )+)/g;
822
+ var NO_FOLLOW = constants.O_NOFOLLOW ?? 0;
823
+ var SECRET_BASENAMES = /* @__PURE__ */ new Set([
824
+ ".env",
825
+ "credentials",
826
+ "credentials.json",
827
+ "id_rsa",
828
+ "id_dsa",
829
+ "id_ecdsa",
830
+ "id_ed25519"
831
+ ]);
832
+ var SECRET_EXTENSIONS = /* @__PURE__ */ new Set([".key", ".pem", ".p12", ".pfx"]);
822
833
  function shortenPath(path, cwd, home) {
823
- if (cwd && path.startsWith(cwd)) {
834
+ if (cwd && (path === cwd || path.startsWith(`${cwd}${sep}`))) {
824
835
  const rest = path.slice(cwd.length).replace(/^[/\\]+/, "");
825
836
  return rest ? `./${rest}` : ".";
826
837
  }
827
- return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
828
- }
829
- function dirChain(cwd) {
830
- const chain = [];
831
- let current = cwd;
832
- for (let i = 0; i < 256; i++) {
833
- chain.push(current);
834
- const parent = dirname(current);
835
- if (parent === current || current === parse(current).root) break;
836
- current = parent;
837
- }
838
- return chain.reverse();
839
- }
840
- function safeRealpath(path) {
841
- try {
842
- return realpathSync(path);
843
- } catch {
844
- return path;
845
- }
838
+ return home && (path === home || path.startsWith(`${home}${sep}`)) ? `~${path.slice(home.length)}` : path;
846
839
  }
847
840
  function isTextFile(path) {
848
841
  const extension = extname(path).toLowerCase();
849
842
  return !extension || TEXT_FILE_EXTENSIONS.has(extension);
850
843
  }
844
+ function isLikelySecretFile(path) {
845
+ const name = basename(path).toLowerCase();
846
+ return SECRET_BASENAMES.has(name) || name.startsWith(".env.") || SECRET_EXTENSIONS.has(extname(name));
847
+ }
851
848
  function readFileBody(path) {
852
- if (!existsSync(path)) return null;
849
+ let fd;
853
850
  try {
854
- const body = readFileSync(path, "utf8").trim();
851
+ const before = lstatSync(path);
852
+ if (before.isSymbolicLink() || !before.isFile()) return null;
853
+ fd = openSync(path, constants.O_RDONLY | NO_FOLLOW);
854
+ if (!fstatSync(fd).isFile()) return null;
855
+ const body = readFileSync(fd, "utf8").trim();
855
856
  return body || null;
856
857
  } catch {
857
858
  return null;
859
+ } finally {
860
+ if (fd !== void 0) closeSync(fd);
858
861
  }
859
862
  }
860
863
  function extractImports(body, baseDir, home) {
@@ -873,23 +876,46 @@ function extractImports(body, baseDir, home) {
873
876
  }
874
877
  return out;
875
878
  }
876
- function loadDocTree(path, cwd, home, seen, depth, maxDepth, maxBytes, out) {
877
- const real = safeRealpath(path);
878
- if (seen.has(real)) return;
879
+ function canonicalExistingFile(path) {
880
+ try {
881
+ const before = lstatSync(path);
882
+ if (before.isSymbolicLink() || !before.isFile()) return null;
883
+ return realpathSync(path);
884
+ } catch {
885
+ return null;
886
+ }
887
+ }
888
+ function isWithin(root, candidate) {
889
+ return candidate === root || candidate.startsWith(`${root}${sep}`);
890
+ }
891
+ function loadTrustedDocTree(path, cwd, home, seen, depth, maxDepth, maxBytes, out, policy) {
892
+ if (isLikelySecretFile(path)) return;
893
+ const real = canonicalExistingFile(path);
894
+ if (real === null || seen.has(real) || isLikelySecretFile(real)) return;
895
+ if (!isWithin(policy.root, real) && !policy.approvedExternal.has(real)) return;
879
896
  seen.add(real);
880
- const body = readFileBody(path);
897
+ const body = readFileBody(real);
881
898
  if (body === null) return;
882
- out.push({ path: shortenPath(path, cwd, home), body: body.length > maxBytes ? body.slice(0, maxBytes) : body });
899
+ out.push({ path: shortenPath(real, cwd, home), body: body.length > maxBytes ? body.slice(0, maxBytes) : body });
883
900
  if (depth >= maxDepth) return;
884
- for (const child of extractImports(body, dirname(path), home)) loadDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out);
901
+ for (const child of extractImports(body, dirname(real), home)) {
902
+ loadTrustedDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out, policy);
903
+ }
885
904
  }
886
- function gatherContextDocs(cwd, home, opts) {
905
+ function gatherContextDocs(cwd, home, opts = {}) {
887
906
  const out = [];
888
907
  try {
908
+ const root = realpathSync(cwd);
909
+ const approvedExternal = /* @__PURE__ */ new Set();
910
+ for (const path of opts.approvedExternalPaths ?? []) {
911
+ const real = canonicalExistingFile(path);
912
+ if (real !== null && !isLikelySecretFile(real)) approvedExternal.add(real);
913
+ }
914
+ const policy = { root, approvedExternal };
889
915
  const seen = /* @__PURE__ */ new Set();
890
- const roots = dirChain(cwd);
891
- if (home) roots.push(home);
892
- for (const dir of roots) for (const name of CANDIDATE_FILES) loadDocTree(join(dir, name), cwd, home, seen, 0, opts?.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH, opts?.maxBytesPerDoc ?? DEFAULT_MAX_BYTES_PER_DOC, out);
916
+ for (const name of CANDIDATE_FILES) {
917
+ loadTrustedDocTree(join(root, name), root, home, seen, 0, opts.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH, opts.maxBytesPerDoc ?? DEFAULT_MAX_BYTES_PER_DOC, out, policy);
918
+ }
893
919
  } catch {
894
920
  return out;
895
921
  }
@@ -1996,7 +2022,139 @@ import { createZeroUsage as createZeroUsage2 } from "indusagi/ai";
1996
2022
 
1997
2023
  // src/conductor/catalog/catalog.ts
1998
2024
  import { z } from "zod";
1999
- import { getModels, getProviders } from "indusagi/ai";
2025
+ import { getModels, getProviders, modelRegistry } from "indusagi/ai";
2026
+
2027
+ // src/models.ts
2028
+ import { ModelRegistry } from "indusagi/ai";
2029
+ var GPT_6_ASTRA_BASE = {
2030
+ id: "gpt-6-astra",
2031
+ name: "GPT-6 Astra",
2032
+ api: "openai-responses",
2033
+ reasoning: true,
2034
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
2035
+ input: ["text", "image"],
2036
+ cost: {
2037
+ input: 10,
2038
+ cacheRead: 1,
2039
+ cacheWrite: 12.5,
2040
+ output: 50
2041
+ },
2042
+ contextWindow: 105e4,
2043
+ maxTokens: 128e3,
2044
+ longContextPricing: {
2045
+ threshold: 272e3,
2046
+ multipliers: {
2047
+ input: 2,
2048
+ cacheRead: 2,
2049
+ cacheWrite: 2,
2050
+ output: 1.5
2051
+ }
2052
+ }
2053
+ };
2054
+ var GPT_6_SOL_BASE = {
2055
+ id: "gpt-6-sol",
2056
+ name: "GPT-6 Sol",
2057
+ api: "openai-responses",
2058
+ reasoning: true,
2059
+ reasoningEfforts: ["high"],
2060
+ input: ["text", "image"],
2061
+ cost: {
2062
+ input: 10,
2063
+ cacheRead: 1,
2064
+ cacheWrite: 12.5,
2065
+ output: 50
2066
+ },
2067
+ contextWindow: 105e4,
2068
+ maxTokens: 128e3,
2069
+ longContextPricing: {
2070
+ threshold: 272e3,
2071
+ multipliers: {
2072
+ input: 2,
2073
+ cacheRead: 2,
2074
+ cacheWrite: 2,
2075
+ output: 1.5
2076
+ }
2077
+ }
2078
+ };
2079
+ var GPT_6_LUNA_BASE = {
2080
+ id: "gpt-6-luna",
2081
+ name: "GPT-6 Luna",
2082
+ api: "openai-responses",
2083
+ reasoning: true,
2084
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
2085
+ input: ["text", "image"],
2086
+ cost: {
2087
+ input: 0.1,
2088
+ cacheRead: 0.01,
2089
+ cacheWrite: 0.125,
2090
+ output: 0.5
2091
+ },
2092
+ contextWindow: 105e4,
2093
+ maxTokens: 128e3,
2094
+ longContextPricing: {
2095
+ threshold: 272e3,
2096
+ multipliers: { input: 2, cacheRead: 2, cacheWrite: 2, output: 1.5 }
2097
+ }
2098
+ };
2099
+ var CODING_AGENT_MODELS = [
2100
+ {
2101
+ ...GPT_6_SOL_BASE,
2102
+ provider: "openai",
2103
+ baseUrl: "https://api.openai.com/v1"
2104
+ },
2105
+ {
2106
+ ...GPT_6_SOL_BASE,
2107
+ api: "openai-codex-responses",
2108
+ provider: "openai-codex",
2109
+ baseUrl: "https://chatgpt.com/backend-api"
2110
+ },
2111
+ {
2112
+ ...GPT_6_LUNA_BASE,
2113
+ provider: "openai",
2114
+ baseUrl: "https://api.openai.com/v1"
2115
+ },
2116
+ {
2117
+ ...GPT_6_LUNA_BASE,
2118
+ api: "openai-codex-responses",
2119
+ provider: "openai-codex",
2120
+ baseUrl: "https://chatgpt.com/backend-api"
2121
+ },
2122
+ {
2123
+ ...GPT_6_ASTRA_BASE,
2124
+ provider: "openai",
2125
+ baseUrl: "https://api.openai.com/v1"
2126
+ },
2127
+ {
2128
+ ...GPT_6_ASTRA_BASE,
2129
+ api: "openai-codex-responses",
2130
+ provider: "openai-codex",
2131
+ baseUrl: "https://chatgpt.com/backend-api"
2132
+ },
2133
+ {
2134
+ ...GPT_6_ASTRA_BASE,
2135
+ provider: "codex-cli",
2136
+ baseUrl: "bridge:codex-cli"
2137
+ },
2138
+ {
2139
+ ...GPT_6_ASTRA_BASE,
2140
+ id: "openai/gpt-6-astra",
2141
+ provider: "indusagi-cli",
2142
+ baseUrl: "bridge:indusagi-cli"
2143
+ },
2144
+ {
2145
+ ...GPT_6_ASTRA_BASE,
2146
+ id: "openai-codex/gpt-6-astra",
2147
+ api: "openai-codex-responses",
2148
+ provider: "indusagi-cli",
2149
+ baseUrl: "bridge:indusagi-cli"
2150
+ }
2151
+ ];
2152
+ function applyCodingAgentModelOverlay(registry) {
2153
+ for (const model of CODING_AGENT_MODELS) registry.registerCustomModel(model);
2154
+ return registry;
2155
+ }
2156
+
2157
+ // src/conductor/catalog/catalog.ts
2000
2158
  var rawModelGate = z.object({
2001
2159
  id: z.string().min(1),
2002
2160
  name: z.string().min(1),
@@ -2005,6 +2163,7 @@ var rawModelGate = z.object({
2005
2163
  input: z.array(z.string()).optional(),
2006
2164
  contextWindow: z.number().optional()
2007
2165
  });
2166
+ applyCodingAgentModelOverlay(modelRegistry);
2008
2167
 
2009
2168
  // src/conductor/signal-hub/translate.ts
2010
2169
  import { createZeroUsage } from "indusagi/ai";
@@ -2015,6 +2174,23 @@ import { ulid } from "ulid";
2015
2174
  // src/conductor/transcript-store/serialize.ts
2016
2175
  import { buildSessionContext, convertToLlm, loadEntriesFromFile, parseSessionEntries } from "indusagi/agent";
2017
2176
 
2177
+ // src/security/private-state.ts
2178
+ import {
2179
+ chmodSync,
2180
+ closeSync as closeSync2,
2181
+ constants as constants2,
2182
+ fchmodSync,
2183
+ fstatSync as fstatSync2,
2184
+ lstatSync as lstatSync2,
2185
+ mkdirSync as mkdirSync2,
2186
+ openSync as openSync2,
2187
+ readFileSync as readFileSync2,
2188
+ renameSync,
2189
+ rmSync as rmSync2,
2190
+ writeFileSync as writeFileSync2
2191
+ } from "node:fs";
2192
+ var NO_FOLLOW2 = constants2.O_NOFOLLOW ?? 0;
2193
+
2018
2194
  // src/window-budget/summarize/prompt.ts
2019
2195
  var CONDENSER_BRIEF = [
2020
2196
  "You are a transcript recorder for a long-running coding session.",