@theokit/sdk 4.27.0 → 4.29.0

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 (99) hide show
  1. package/dist/{cron-DPUZzncH.d.ts → agent-BZz4X8ZM.d.ts} +10 -577
  2. package/dist/{cron-DzZ9TMa2.d.cts → agent-CdjSCbYz.d.cts} +10 -577
  3. package/dist/agent-FBTLZI6V.js +41 -0
  4. package/dist/agent-FBTLZI6V.js.map +1 -0
  5. package/dist/agent-LBCEK6VH.cjs +50 -0
  6. package/dist/agent-LBCEK6VH.cjs.map +1 -0
  7. package/dist/{chunk-WBBYSW3Q.cjs → chunk-54YLX4Q6.cjs} +5 -5
  8. package/dist/{chunk-WBBYSW3Q.cjs.map → chunk-54YLX4Q6.cjs.map} +1 -1
  9. package/dist/{chunk-AKSBP7OO.js → chunk-5IB2UKSN.js} +5 -41
  10. package/dist/chunk-5IB2UKSN.js.map +1 -0
  11. package/dist/chunk-BNIFFFH3.js +129 -0
  12. package/dist/chunk-BNIFFFH3.js.map +1 -0
  13. package/dist/{chunk-LSLPF6OI.cjs → chunk-DHLZIMP6.cjs} +5 -43
  14. package/dist/chunk-DHLZIMP6.cjs.map +1 -0
  15. package/dist/{chunk-OX772BEW.js → chunk-EULKRTB5.js} +3 -3
  16. package/dist/{chunk-OX772BEW.js.map → chunk-EULKRTB5.js.map} +1 -1
  17. package/dist/chunk-FKCC2MED.js +43 -0
  18. package/dist/chunk-FKCC2MED.js.map +1 -0
  19. package/dist/{chunk-QUTUJ6P6.cjs → chunk-HK7ZHSAX.cjs} +25 -16
  20. package/dist/chunk-HK7ZHSAX.cjs.map +1 -0
  21. package/dist/{chunk-MQLCZ4HJ.cjs → chunk-JLMXKTBX.cjs} +4 -4
  22. package/dist/{chunk-MQLCZ4HJ.cjs.map → chunk-JLMXKTBX.cjs.map} +1 -1
  23. package/dist/chunk-LALRCFSN.cjs +46 -0
  24. package/dist/chunk-LALRCFSN.cjs.map +1 -0
  25. package/dist/{chunk-DUK5WMKJ.js → chunk-R6ZP6KNB.js} +25 -16
  26. package/dist/chunk-R6ZP6KNB.js.map +1 -0
  27. package/dist/chunk-RZQ6E5YZ.cjs +131 -0
  28. package/dist/chunk-RZQ6E5YZ.cjs.map +1 -0
  29. package/dist/{chunk-6M76Z3AI.js → chunk-VUJ3NVDC.js} +692 -808
  30. package/dist/chunk-VUJ3NVDC.js.map +1 -0
  31. package/dist/{chunk-G6GO6BXD.js → chunk-XAUBQMQU.js} +3 -3
  32. package/dist/{chunk-G6GO6BXD.js.map → chunk-XAUBQMQU.js.map} +1 -1
  33. package/dist/{chunk-TMNS2LOS.cjs → chunk-YQAPEYUV.cjs} +715 -831
  34. package/dist/chunk-YQAPEYUV.cjs.map +1 -0
  35. package/dist/compact-session-OO2GD76G.js +1 -1
  36. package/dist/compact-session-OO7NVWPB.cjs +1 -1
  37. package/dist/cron-CIIshMs3.d.cts +579 -0
  38. package/dist/cron-dElBjDaL.d.ts +579 -0
  39. package/dist/cron.cjs +13 -11
  40. package/dist/cron.d.cts +2 -1
  41. package/dist/cron.d.ts +2 -1
  42. package/dist/cron.js +12 -10
  43. package/dist/errors.d.ts +3 -424
  44. package/dist/eval.cjs +17 -15
  45. package/dist/eval.cjs.map +1 -1
  46. package/dist/eval.js +13 -11
  47. package/dist/eval.js.map +1 -1
  48. package/dist/{index-manager-CB6I7XFQ.cjs → index-manager-H7HUMW52.cjs} +5 -5
  49. package/dist/{index-manager-CB6I7XFQ.cjs.map → index-manager-H7HUMW52.cjs.map} +1 -1
  50. package/dist/{index-manager-L27Q3QAC.js → index-manager-P7ZUX2RG.js} +4 -4
  51. package/dist/{index-manager-L27Q3QAC.js.map → index-manager-P7ZUX2RG.js.map} +1 -1
  52. package/dist/index.cjs +66 -45
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +18 -4
  55. package/dist/index.d.ts +18 -4
  56. package/dist/index.js +42 -21
  57. package/dist/index.js.map +1 -1
  58. package/dist/internal/persistence/index.cjs +10 -10
  59. package/dist/internal/persistence/index.js +2 -2
  60. package/dist/internal/persistence/jsonl.d.cts +7 -0
  61. package/dist/internal/persistence/jsonl.d.ts +7 -0
  62. package/dist/internal/persistence/session-writer.d.cts +56 -0
  63. package/dist/internal/persistence/session-writer.d.ts +56 -0
  64. package/dist/internal/persistence/transcript-ops.d.cts +71 -0
  65. package/dist/internal/persistence/transcript-ops.d.ts +71 -0
  66. package/dist/persistence.cjs +115 -12
  67. package/dist/persistence.cjs.map +1 -1
  68. package/dist/persistence.d.cts +2 -0
  69. package/dist/persistence.d.ts +2 -0
  70. package/dist/persistence.js +105 -3
  71. package/dist/persistence.js.map +1 -1
  72. package/dist/{run-until-K7IO7ZPQ.js → run-until-YZ2ODWOF.js} +3 -2
  73. package/dist/run-until-YZ2ODWOF.js.map +1 -0
  74. package/dist/{run-until-ZD4PRLNJ.cjs → run-until-ZZ2JLPJM.cjs} +3 -2
  75. package/dist/run-until-ZZ2JLPJM.cjs.map +1 -0
  76. package/dist/server/auth/index.d.cts +207 -11
  77. package/dist/server/auth/index.d.ts +207 -11
  78. package/dist/server/errors-envelope.d.cts +12 -6
  79. package/dist/server/errors-envelope.d.ts +12 -6
  80. package/dist/skills.cjs +4 -3
  81. package/dist/skills.js +2 -1
  82. package/dist/subagents-loader.cjs +19 -0
  83. package/dist/subagents-loader.cjs.map +1 -0
  84. package/dist/subagents-loader.d.cts +39 -0
  85. package/dist/subagents-loader.d.ts +39 -0
  86. package/dist/subagents-loader.js +16 -0
  87. package/dist/subagents-loader.js.map +1 -0
  88. package/dist/types/goal-events.d.ts +8 -0
  89. package/dist/workflow.cjs +1 -1
  90. package/dist/workflow.js +1 -1
  91. package/package.json +12 -2
  92. package/dist/chunk-6M76Z3AI.js.map +0 -1
  93. package/dist/chunk-AKSBP7OO.js.map +0 -1
  94. package/dist/chunk-DUK5WMKJ.js.map +0 -1
  95. package/dist/chunk-LSLPF6OI.cjs.map +0 -1
  96. package/dist/chunk-QUTUJ6P6.cjs.map +0 -1
  97. package/dist/chunk-TMNS2LOS.cjs.map +0 -1
  98. package/dist/run-until-K7IO7ZPQ.js.map +0 -1
  99. package/dist/run-until-ZD4PRLNJ.cjs.map +0 -1
@@ -1,11 +1,113 @@
1
- export { JsonlParseError, appendJsonl, loadJsonl, readJsonlIds } from './chunk-DUK5WMKJ.js';
1
+ export { JsonlParseError, appendJsonl, loadJsonl, readJsonlIds } from './chunk-R6ZP6KNB.js';
2
2
  export { PersistenceSchema } from './chunk-CAH3G4IS.js';
3
- export { applyWalWithFallback, isCorruptionError, openSqliteResilient, sanitizeFts5Query } from './chunk-77FOZCOT.js';
4
3
  export { withFileLock } from './chunk-HZQEDNF6.js';
4
+ export { applyWalWithFallback, isCorruptionError, openSqliteResilient, sanitizeFts5Query } from './chunk-77FOZCOT.js';
5
5
  export { withCwdMutex } from './chunk-2XLKLVVR.js';
6
6
  export { encodeProjectDir, transcriptPath } from './chunk-ZZWTY3RX.js';
7
7
  export { atomicWriteJson, atomicWriteText, replaceFileAtomic } from './chunk-S2BBRVQX.js';
8
- import './chunk-WLKFWQSJ.js';
8
+ import { TheokitAgentError } from './chunk-WLKFWQSJ.js';
9
9
  import './chunk-JTFYYKDT.js';
10
+ import { openSync, closeSync, rmSync, readFileSync, writeSync, statSync, readSync } from 'fs';
11
+
12
+ var SessionBusyError = class extends TheokitAgentError {
13
+ constructor(sessionPath) {
14
+ super(
15
+ `another process is already writing this session: ${sessionPath}. Fork it to a new id instead of appending \u2014 two writers interleave lines into a sequence that parses but is not what either process wrote.`,
16
+ { code: "session_busy", isRetryable: false }
17
+ );
18
+ this.sessionPath = sessionPath;
19
+ }
20
+ sessionPath;
21
+ name = "SessionBusyError";
22
+ };
23
+ async function acquireSessionWriter(sessionPath) {
24
+ const lockPath = `${sessionPath}.lock`;
25
+ let fd;
26
+ try {
27
+ fd = openSync(lockPath, "wx");
28
+ } catch (err) {
29
+ if (err.code === "EEXIST") {
30
+ throw new SessionBusyError(sessionPath);
31
+ }
32
+ throw err;
33
+ }
34
+ closeSync(fd);
35
+ let released = false;
36
+ return {
37
+ sessionPath,
38
+ release: async () => {
39
+ if (released) return;
40
+ released = true;
41
+ rmSync(lockPath, { force: true });
42
+ }
43
+ };
44
+ }
45
+ var LiveSessionError = class extends TheokitAgentError {
46
+ constructor(path) {
47
+ super(
48
+ `refusing to write over a live session transcript: ${path}. Fork to a new id instead \u2014 the live pointer, the most recent transcript and any active registry entry are never overwritten (audit-trail rotation, M60).`,
49
+ { code: "live_session_protected", isRetryable: false }
50
+ );
51
+ this.path = path;
52
+ }
53
+ path;
54
+ name = "LiveSessionError";
55
+ };
56
+ function forkTranscript(src, dst, options = {}) {
57
+ for (const live of options.liveSessionPaths ?? []) {
58
+ if (live === dst) throw new LiveSessionError(dst);
59
+ }
60
+ const lines = readFileSync(src, "utf8").split("\n").filter((l) => l.trim().length > 0);
61
+ const kept = options.beforeRecordIndex === void 0 ? lines : lines.slice(0, options.beforeRecordIndex);
62
+ const body = kept.length > 0 ? `${kept.join("\n")}
63
+ ` : "";
64
+ const fd = openSync(dst, "wx");
65
+ try {
66
+ writeSync(fd, body);
67
+ } finally {
68
+ closeSync(fd);
69
+ }
70
+ }
71
+ var TAIL_CHUNK = 64 * 1024;
72
+ function lerCaudaBruta(path, want) {
73
+ const size = statSync(path).size;
74
+ const fd = openSync(path, "r");
75
+ let bytesRead = 0;
76
+ let tail = "";
77
+ let pos = size;
78
+ try {
79
+ while (pos > 0) {
80
+ const len = Math.min(TAIL_CHUNK, pos);
81
+ pos -= len;
82
+ const buf = Buffer.alloc(len);
83
+ readSync(fd, buf, 0, len, pos);
84
+ bytesRead += len;
85
+ tail = buf.toString("utf8") + tail;
86
+ if (naoVazias(tail).length > want) break;
87
+ }
88
+ } finally {
89
+ closeSync(fd);
90
+ }
91
+ const linhas = naoVazias(tail);
92
+ return { linhas: pos > 0 ? linhas.slice(1) : linhas, bytesRead };
93
+ }
94
+ function naoVazias(texto) {
95
+ return texto.split("\n").filter((l) => l.trim().length > 0);
96
+ }
97
+ function readJsonlTail(path, options = {}) {
98
+ const want = options.maxRecords ?? Number.POSITIVE_INFINITY;
99
+ const { linhas, bytesRead } = lerCaudaBruta(path, want);
100
+ let sel = linhas;
101
+ if (options.sinceMarker !== void 0) {
102
+ const marcador = options.sinceMarker;
103
+ const idx = sel.findLastIndex((l) => l.includes(marcador));
104
+ if (idx >= 0) sel = sel.slice(idx + 1);
105
+ }
106
+ if (Number.isFinite(want)) sel = sel.slice(-want);
107
+ const out = sel.map((l) => JSON.parse(l));
108
+ return options._stats === true ? Object.assign(out, { bytesRead }) : out;
109
+ }
110
+
111
+ export { LiveSessionError, SessionBusyError, acquireSessionWriter, forkTranscript, readJsonlTail };
10
112
  //# sourceMappingURL=persistence.js.map
11
113
  //# sourceMappingURL=persistence.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"persistence.js"}
1
+ {"version":3,"sources":["../src/internal/persistence/session-writer.ts","../src/internal/persistence/transcript-ops.ts"],"names":["openSync","closeSync"],"mappings":"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","file":"persistence.js","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport { closeSync, openSync, rmSync } from \"node:fs\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n const lockPath = `${sessionPath}.lock`;\n let fd: number;\n try {\n fd = openSync(lockPath, \"wx\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n throw new SessionBusyError(sessionPath);\n }\n throw err;\n }\n closeSync(fd);\n\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n };\n}\n","/**\n * M81 — transcript operations the consumer was doing by hand INSIDE the framework's own store.\n *\n * ## What this replaces\n *\n * `agents/lib/session/backtrack.ts:188` (agent-builder) wrote straight into the session store with a\n * bare `writeFileSync` — no atomicity, no lock, no API. 243 lines re-implementing parse, cut and\n * write for a format the framework owns. The consumer is not at fault: nothing here was reachable.\n *\n * ## The rule that travels WITH the operation\n *\n * `rules/audit-trail-rotation.md § Session transcripts (M60)` defines a NEVER-delete list — the live\n * pointer, the most recent transcript, and any active registry entry. That rule lived in the\n * CONSUMER. Moving the operation here without moving the rule would ship an API able to destroy\n * exactly what the rule protects — the same shape of defect as M80's `reconcileUpdateGoalStatus`:\n * critical knowledge outside the module that needs it, applied by convention.\n *\n * So `forkTranscript` takes `liveSessionPaths` and refuses, with a TYPED error, to write over any of\n * them. The caller supplies the list because only the caller knows which session is live; the\n * enforcement lives here because that is where the write happens.\n *\n * @internal\n */\n\nimport { closeSync, openSync, readFileSync, readSync, statSync, writeSync } from \"node:fs\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — the target is a protected session (live pointer / most-recent transcript / active entry).\n *\n * Typed rather than a bare `Error` because the caller must distinguish \"this session is protected\"\n * from \"the disk is full\": the first is a correct refusal, the second is an incident.\n */\nexport class LiveSessionError extends TheokitAgentError {\n override readonly name = \"LiveSessionError\";\n\n constructor(readonly path: string) {\n super(\n `refusing to write over a live session transcript: ${path}. ` +\n \"Fork to a new id instead — the live pointer, the most recent transcript and any active \" +\n \"registry entry are never overwritten (audit-trail rotation, M60).\",\n { code: \"live_session_protected\", isRetryable: false },\n );\n }\n}\n\n/** Options for {@link forkTranscript}. */\nexport interface ForkTranscriptOptions {\n /** Keep records `[0, beforeRecordIndex)`. Omit to copy the whole transcript. */\n readonly beforeRecordIndex?: number;\n /**\n * Paths that must never be written over — the live pointer, the most recent transcript, any active\n * registry entry. The caller supplies them because only the caller knows which session is live.\n */\n readonly liveSessionPaths?: readonly string[];\n}\n\n/**\n * Copy `src` into `dst`, keeping the first `beforeRecordIndex` records. The SOURCE is never touched.\n *\n * Atomicity comes from `wx` (exclusive create): two concurrent forks to the same destination cannot\n * both succeed — the loser gets `EEXIST` rather than writing over a half-written file. That is also\n * why an existing destination is a refusal, not a silent overwrite: losing a transcript without an\n * error is the worst failure mode for an operation that touches user sessions.\n */\nexport function forkTranscript(\n src: string,\n dst: string,\n options: ForkTranscriptOptions = {},\n): void {\n for (const live of options.liveSessionPaths ?? []) {\n if (live === dst) throw new LiveSessionError(dst);\n }\n\n const lines = readFileSync(src, \"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim().length > 0);\n const kept =\n options.beforeRecordIndex === undefined ? lines : lines.slice(0, options.beforeRecordIndex);\n const body = kept.length > 0 ? `${kept.join(\"\\n\")}\\n` : \"\";\n\n // `wx` — fails with EEXIST instead of truncating. The exclusivity IS the concurrency guarantee.\n const fd = openSync(dst, \"wx\");\n try {\n writeSync(fd, body);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Options for {@link readJsonlTail}. */\nexport interface ReadJsonlTailOptions {\n /** Maximum records to return, counted from the END. */\n readonly maxRecords?: number;\n /** Stop once a line contains this marker (exclusive). */\n readonly sinceMarker?: string;\n /** Test-only: also report how many bytes were read, to prove the read is not whole-file. */\n readonly _stats?: boolean;\n}\n\nconst TAIL_CHUNK = 64 * 1024;\n\n/**\n * Lê chunks de trás para frente até acumular linhas completas suficientes.\n *\n * Extraído de `readJsonlTail` porque o laço de leitura e a seleção de registros são duas\n * responsabilidades — e juntas passavam do teto de complexidade. A primeira linha do buffer pode\n * estar cortada ao meio quando a leitura parou antes do início do arquivo; por isso ela é descartada.\n */\nfunction lerCaudaBruta(path: string, want: number): { linhas: string[]; bytesRead: number } {\n const size = statSync(path).size;\n const fd = openSync(path, \"r\");\n let bytesRead = 0;\n let tail = \"\";\n let pos = size;\n try {\n while (pos > 0) {\n const len = Math.min(TAIL_CHUNK, pos);\n pos -= len;\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, pos);\n bytesRead += len;\n tail = buf.toString(\"utf8\") + tail;\n if (naoVazias(tail).length > want) break;\n }\n } finally {\n closeSync(fd);\n }\n const linhas = naoVazias(tail);\n return { linhas: pos > 0 ? linhas.slice(1) : linhas, bytesRead };\n}\n\n/** Linhas com conteúdo, na ordem do arquivo. */\nfunction naoVazias(texto: string): string[] {\n return texto.split(\"\\n\").filter((l) => l.trim().length > 0);\n}\n\n/**\n * Read the LAST records of a JSONL file without loading the whole thing.\n *\n * Reads fixed-size chunks backwards from EOF until enough newlines have been seen. A session\n * transcript grows without bound; loading megabytes to show the last three turns is the cost this\n * exists to avoid — and a `slice` over a full read would be that same cost with a better name.\n */\nexport function readJsonlTail<T = Record<string, unknown>>(\n path: string,\n options: ReadJsonlTailOptions = {},\n): T[] {\n const want = options.maxRecords ?? Number.POSITIVE_INFINITY;\n const { linhas, bytesRead } = lerCaudaBruta(path, want);\n\n let sel = linhas;\n if (options.sinceMarker !== undefined) {\n const marcador = options.sinceMarker;\n const idx = sel.findLastIndex((l) => l.includes(marcador));\n if (idx >= 0) sel = sel.slice(idx + 1);\n }\n if (Number.isFinite(want)) sel = sel.slice(-want);\n\n const out = sel.map((l) => JSON.parse(l) as T);\n return options._stats === true ? (Object.assign(out, { bytesRead }) as T[]) : out;\n}\n"]}
@@ -59,6 +59,7 @@ async function* runUntilImpl(agent, goal, options, deps) {
59
59
  const judgeOpts = {};
60
60
  if (options?.judgeModel !== void 0) judgeOpts.judgeModel = options.judgeModel;
61
61
  if (options?.judgeApiKey !== void 0) judgeOpts.apiKey = options.judgeApiKey;
62
+ if (options?.agentModel !== void 0) judgeOpts.agentModel = options.agentModel;
62
63
  const judgeCtx = { goal, lastResponse };
63
64
  if (options?.subgoals !== void 0) judgeCtx.subgoals = options.subgoals;
64
65
  const judgment = await deps.judge(judgeCtx, judgeOpts);
@@ -173,5 +174,5 @@ ${lastResponse.slice(-1e3)}`
173
174
  }
174
175
 
175
176
  export { composeContinuation, runUntilImpl };
176
- //# sourceMappingURL=run-until-K7IO7ZPQ.js.map
177
- //# sourceMappingURL=run-until-K7IO7ZPQ.js.map
177
+ //# sourceMappingURL=run-until-YZ2ODWOF.js.map
178
+ //# sourceMappingURL=run-until-YZ2ODWOF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/runtime/lifecycle/run-until.ts"],"names":[],"mappings":"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","file":"run-until-YZ2ODWOF.js","sourcesContent":["/**\n * Goal-driven Ralph loop (T3.2, ADRs D115-D121).\n *\n * `runUntilImpl` is an `AsyncGenerator<GoalEvent, GoalResult, void>`:\n * yields events as the loop progresses, returns the final result when\n * the goal completes, fails, or is paused. The auxiliary judge model is\n * injected via `deps.judge` so the generator stays free of the `Agent`\n * façade import.\n *\n * EC-C fix: pre-aborted signals emit only `[paused]`, not `[active, paused]`.\n * EC-D: `maxTurns: 0` is supported (vacuous yield active → failed).\n *\n * @internal\n */\n\nimport type { SDKAgent } from \"../../../types/agent.js\";\nimport type {\n GoalEvent,\n GoalOptions,\n GoalResult,\n JudgeResult,\n} from \"../../../types/goal-events.js\";\nimport type { JudgeContext, JudgeOptions } from \"../../judge/judge-call.js\";\nimport { GOAL_CONTINUATION_MARKER } from \"./goal-marker.js\";\n\n/** DI contract: the judge is the only external touchpoint. */\nexport interface RunUntilDeps {\n judge: (ctx: JudgeContext, opts?: JudgeOptions) => Promise<JudgeResult>;\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the goal-loop interleaves turn-start, send, judge, continuation, abort-check, and failure-bail — extracting helpers harms the linear narrative.\nexport async function* runUntilImpl(\n agent: SDKAgent,\n goal: string,\n options: GoalOptions | undefined,\n deps: RunUntilDeps,\n): AsyncGenerator<GoalEvent, GoalResult, void> {\n const maxTurns = options?.maxTurns ?? 20;\n const maxFails = options?.maxConsecutiveJudgeFailures ?? 3;\n const tokenBudget = options?.tokenBudget; // M55 — undefined ⇒ ilimitado\n const signal = options?.signal;\n // Use a function (not direct property access) so TS does not narrow\n // `signal.aborted` to `false | undefined` after the initial check.\n // AbortSignal.aborted is a getter that can flip true between calls.\n // biome-ignore lint/complexity/useOptionalChain: optional-chain would re-narrow signal?.aborted to `false | undefined` after the first check, defeating the purpose of this helper.\n const isAborted = (): boolean => signal !== undefined && signal.aborted;\n let turn = 0;\n let consecutiveFailures = 0;\n let tokensUsed = 0; // M55 — soma observada (0 se usage ausente — fail-open)\n let lastResponse = \"\";\n\n // EC-C: signal already aborted BEFORE first event → emit only [paused].\n if (isAborted()) {\n yield {\n type: \"status_change\",\n status: \"paused\",\n reason: \"aborted via AbortSignal before first turn\",\n };\n return { status: \"paused\", turnsUsed: 0, tokensUsed, finalResponse: undefined };\n }\n\n yield { type: \"status_change\", status: \"active\", reason: \"Goal started\" };\n\n while (turn < maxTurns) {\n if (isAborted()) {\n yield { type: \"status_change\", status: \"paused\", reason: \"aborted via AbortSignal\" };\n return {\n status: \"paused\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n\n turn += 1;\n yield { type: \"turn_start\", turn, goal };\n\n const continuationPrompt = turn === 1 ? goal : composeContinuation(goal, lastResponse);\n\n const run = await agent.send(continuationPrompt);\n // M55 review HIGH — thread the abort INTO the in-flight run: without this, Esc mid-turn is\n // cosmetic (the turn keeps mutating the workspace until it finishes on its own). `run.cancel()`\n // aborts the stream + in-flight tool calls (types/run.ts contract).\n const cancelOnAbort = (): void => {\n void (run as { cancel?: () => Promise<void> }).cancel?.();\n };\n if (signal !== undefined) {\n if (signal.aborted) cancelOnAbort();\n else signal.addEventListener(\"abort\", cancelOnAbort, { once: true });\n }\n const result = await run.wait();\n if (signal !== undefined) signal.removeEventListener(\"abort\", cancelOnAbort);\n lastResponse = result.result ?? \"\";\n // M55 — token accounting fail-open: só soma quando o run reporta usage.\n const turnTokens = (result as { usage?: { totalTokens?: number } }).usage?.totalTokens;\n if (typeof turnTokens === \"number\") tokensUsed += turnTokens;\n // Re-check aborted right after the turn lands: a cancelled turn must NOT spend a judge call.\n if (isAborted()) {\n yield { type: \"status_change\", status: \"paused\", reason: \"aborted via AbortSignal\" };\n return {\n status: \"paused\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n yield { type: \"agent_response\", turn, content: lastResponse };\n\n const judgeOpts: JudgeOptions = {};\n if (options?.judgeModel !== undefined) judgeOpts.judgeModel = options.judgeModel;\n if (options?.judgeApiKey !== undefined) judgeOpts.apiKey = options.judgeApiKey;\n // M80 — encaminha o modelo do agente conduzido para o judge derivar quando `judgeModel` é omitido.\n if (options?.agentModel !== undefined) judgeOpts.agentModel = options.agentModel;\n const judgeCtx: JudgeContext = { goal, lastResponse };\n if (options?.subgoals !== undefined) judgeCtx.subgoals = options.subgoals;\n const judgment = await deps.judge(judgeCtx, judgeOpts);\n yield {\n type: \"judge_verdict\",\n turn,\n verdict: judgment.verdict,\n reason: judgment.reason,\n parseFailed: judgment.parseFailed,\n };\n\n if (judgment.parseFailed) {\n consecutiveFailures += 1;\n if (consecutiveFailures >= maxFails) {\n yield {\n type: \"status_change\",\n status: \"failed\",\n reason: `judge model too unreliable (${consecutiveFailures} parse failures in a row)`,\n };\n return {\n status: \"failed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n } else {\n consecutiveFailures = 0;\n }\n\n if (judgment.verdict === \"done\") {\n yield { type: \"status_change\", status: \"completed\", reason: judgment.reason };\n return {\n status: \"completed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n // M80 — o braço que faltava. Sem ele, um judge que reconhecesse impossibilidade só podia dizer\n // \"continue\", e o loop repetia o mesmo turno até estourar o orçamento — reportando `failed` por\n // limite em vez de `blocked` por impossibilidade. Duas causas distintas com o mesmo desfecho\n // visível, e o consumidor precisava reconciliar depois do loop para distingui-las.\n if (judgment.verdict === \"blocked\") {\n yield { type: \"status_change\", status: \"blocked\", reason: judgment.reason };\n return {\n status: \"blocked\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n if (judgment.verdict === \"skipped\") {\n yield {\n type: \"status_change\",\n status: \"completed\",\n reason: `skipped: ${judgment.reason}`,\n };\n return {\n status: \"completed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n\n // M55 — checagem de token budget APÓS o turno (Codex: wind-down suave, para o loop).\n if (tokenBudget !== undefined && tokensUsed >= tokenBudget) {\n yield {\n type: \"status_change\",\n status: \"budget_limited\",\n reason: `token budget (${tokenBudget}) reached: ${tokensUsed} used`,\n };\n return {\n status: \"budget_limited\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n\n yield { type: \"continuation\", turn, prompt: continuationPrompt };\n }\n\n yield {\n type: \"status_change\",\n status: \"failed\",\n reason: `max turns (${maxTurns}) exhausted`,\n };\n return {\n status: \"failed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n}\n\n/**\n * Build the continuation user message for turns 2+. Keeps last response\n * preview short so we don't bloat the prompt cache.\n *\n * @internal\n */\n\nexport function composeContinuation(goal: string, lastResponse: string): string {\n // M55 — Codex-faithful continuation (ext/goal templates/goals/continuation.md): keep the FULL\n // objective intact, work from current-state evidence, and audit completion requirement-by-requirement\n // before declaring done. Improves the quality of what the judge then evaluates.\n return [\n GOAL_CONTINUATION_MARKER,\n \"Continue working toward the active goal.\",\n \"\",\n `<objective>\\n${goal}\\n</objective>`,\n \"\",\n \"Continuation behavior:\",\n \"- This goal persists across turns. Keep the full objective intact; if it cannot be finished now,\",\n \" make concrete progress toward the real requested end state and do not redefine success around a\",\n \" smaller or easier task.\",\n \"\",\n \"Work from evidence:\",\n \"- Use the current worktree and external state as authoritative. Inspect the current state before\",\n \" relying on it. Improve, replace, or remove existing work as needed to satisfy the actual objective.\",\n \"\",\n \"Completion audit:\",\n \"- Before deciding the goal is achieved, treat completion as unproven and verify it against the actual\",\n \" current state, requirement by requirement. Treat uncertain or indirect evidence as not achieved.\",\n \"\",\n `Your last response was:\\n${lastResponse.slice(-1000)}`,\n ].join(\"\\n\");\n}\n"]}
@@ -61,6 +61,7 @@ async function* runUntilImpl(agent, goal, options, deps) {
61
61
  const judgeOpts = {};
62
62
  if (options?.judgeModel !== void 0) judgeOpts.judgeModel = options.judgeModel;
63
63
  if (options?.judgeApiKey !== void 0) judgeOpts.apiKey = options.judgeApiKey;
64
+ if (options?.agentModel !== void 0) judgeOpts.agentModel = options.agentModel;
64
65
  const judgeCtx = { goal, lastResponse };
65
66
  if (options?.subgoals !== void 0) judgeCtx.subgoals = options.subgoals;
66
67
  const judgment = await deps.judge(judgeCtx, judgeOpts);
@@ -176,5 +177,5 @@ ${lastResponse.slice(-1e3)}`
176
177
 
177
178
  exports.composeContinuation = composeContinuation;
178
179
  exports.runUntilImpl = runUntilImpl;
179
- //# sourceMappingURL=run-until-ZD4PRLNJ.cjs.map
180
- //# sourceMappingURL=run-until-ZD4PRLNJ.cjs.map
180
+ //# sourceMappingURL=run-until-ZZ2JLPJM.cjs.map
181
+ //# sourceMappingURL=run-until-ZZ2JLPJM.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/runtime/lifecycle/run-until.ts"],"names":["GOAL_CONTINUATION_MARKER"],"mappings":"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","file":"run-until-ZZ2JLPJM.cjs","sourcesContent":["/**\n * Goal-driven Ralph loop (T3.2, ADRs D115-D121).\n *\n * `runUntilImpl` is an `AsyncGenerator<GoalEvent, GoalResult, void>`:\n * yields events as the loop progresses, returns the final result when\n * the goal completes, fails, or is paused. The auxiliary judge model is\n * injected via `deps.judge` so the generator stays free of the `Agent`\n * façade import.\n *\n * EC-C fix: pre-aborted signals emit only `[paused]`, not `[active, paused]`.\n * EC-D: `maxTurns: 0` is supported (vacuous yield active → failed).\n *\n * @internal\n */\n\nimport type { SDKAgent } from \"../../../types/agent.js\";\nimport type {\n GoalEvent,\n GoalOptions,\n GoalResult,\n JudgeResult,\n} from \"../../../types/goal-events.js\";\nimport type { JudgeContext, JudgeOptions } from \"../../judge/judge-call.js\";\nimport { GOAL_CONTINUATION_MARKER } from \"./goal-marker.js\";\n\n/** DI contract: the judge is the only external touchpoint. */\nexport interface RunUntilDeps {\n judge: (ctx: JudgeContext, opts?: JudgeOptions) => Promise<JudgeResult>;\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the goal-loop interleaves turn-start, send, judge, continuation, abort-check, and failure-bail — extracting helpers harms the linear narrative.\nexport async function* runUntilImpl(\n agent: SDKAgent,\n goal: string,\n options: GoalOptions | undefined,\n deps: RunUntilDeps,\n): AsyncGenerator<GoalEvent, GoalResult, void> {\n const maxTurns = options?.maxTurns ?? 20;\n const maxFails = options?.maxConsecutiveJudgeFailures ?? 3;\n const tokenBudget = options?.tokenBudget; // M55 — undefined ⇒ ilimitado\n const signal = options?.signal;\n // Use a function (not direct property access) so TS does not narrow\n // `signal.aborted` to `false | undefined` after the initial check.\n // AbortSignal.aborted is a getter that can flip true between calls.\n // biome-ignore lint/complexity/useOptionalChain: optional-chain would re-narrow signal?.aborted to `false | undefined` after the first check, defeating the purpose of this helper.\n const isAborted = (): boolean => signal !== undefined && signal.aborted;\n let turn = 0;\n let consecutiveFailures = 0;\n let tokensUsed = 0; // M55 — soma observada (0 se usage ausente — fail-open)\n let lastResponse = \"\";\n\n // EC-C: signal already aborted BEFORE first event → emit only [paused].\n if (isAborted()) {\n yield {\n type: \"status_change\",\n status: \"paused\",\n reason: \"aborted via AbortSignal before first turn\",\n };\n return { status: \"paused\", turnsUsed: 0, tokensUsed, finalResponse: undefined };\n }\n\n yield { type: \"status_change\", status: \"active\", reason: \"Goal started\" };\n\n while (turn < maxTurns) {\n if (isAborted()) {\n yield { type: \"status_change\", status: \"paused\", reason: \"aborted via AbortSignal\" };\n return {\n status: \"paused\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n\n turn += 1;\n yield { type: \"turn_start\", turn, goal };\n\n const continuationPrompt = turn === 1 ? goal : composeContinuation(goal, lastResponse);\n\n const run = await agent.send(continuationPrompt);\n // M55 review HIGH — thread the abort INTO the in-flight run: without this, Esc mid-turn is\n // cosmetic (the turn keeps mutating the workspace until it finishes on its own). `run.cancel()`\n // aborts the stream + in-flight tool calls (types/run.ts contract).\n const cancelOnAbort = (): void => {\n void (run as { cancel?: () => Promise<void> }).cancel?.();\n };\n if (signal !== undefined) {\n if (signal.aborted) cancelOnAbort();\n else signal.addEventListener(\"abort\", cancelOnAbort, { once: true });\n }\n const result = await run.wait();\n if (signal !== undefined) signal.removeEventListener(\"abort\", cancelOnAbort);\n lastResponse = result.result ?? \"\";\n // M55 — token accounting fail-open: só soma quando o run reporta usage.\n const turnTokens = (result as { usage?: { totalTokens?: number } }).usage?.totalTokens;\n if (typeof turnTokens === \"number\") tokensUsed += turnTokens;\n // Re-check aborted right after the turn lands: a cancelled turn must NOT spend a judge call.\n if (isAborted()) {\n yield { type: \"status_change\", status: \"paused\", reason: \"aborted via AbortSignal\" };\n return {\n status: \"paused\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n yield { type: \"agent_response\", turn, content: lastResponse };\n\n const judgeOpts: JudgeOptions = {};\n if (options?.judgeModel !== undefined) judgeOpts.judgeModel = options.judgeModel;\n if (options?.judgeApiKey !== undefined) judgeOpts.apiKey = options.judgeApiKey;\n // M80 — encaminha o modelo do agente conduzido para o judge derivar quando `judgeModel` é omitido.\n if (options?.agentModel !== undefined) judgeOpts.agentModel = options.agentModel;\n const judgeCtx: JudgeContext = { goal, lastResponse };\n if (options?.subgoals !== undefined) judgeCtx.subgoals = options.subgoals;\n const judgment = await deps.judge(judgeCtx, judgeOpts);\n yield {\n type: \"judge_verdict\",\n turn,\n verdict: judgment.verdict,\n reason: judgment.reason,\n parseFailed: judgment.parseFailed,\n };\n\n if (judgment.parseFailed) {\n consecutiveFailures += 1;\n if (consecutiveFailures >= maxFails) {\n yield {\n type: \"status_change\",\n status: \"failed\",\n reason: `judge model too unreliable (${consecutiveFailures} parse failures in a row)`,\n };\n return {\n status: \"failed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n } else {\n consecutiveFailures = 0;\n }\n\n if (judgment.verdict === \"done\") {\n yield { type: \"status_change\", status: \"completed\", reason: judgment.reason };\n return {\n status: \"completed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n // M80 — o braço que faltava. Sem ele, um judge que reconhecesse impossibilidade só podia dizer\n // \"continue\", e o loop repetia o mesmo turno até estourar o orçamento — reportando `failed` por\n // limite em vez de `blocked` por impossibilidade. Duas causas distintas com o mesmo desfecho\n // visível, e o consumidor precisava reconciliar depois do loop para distingui-las.\n if (judgment.verdict === \"blocked\") {\n yield { type: \"status_change\", status: \"blocked\", reason: judgment.reason };\n return {\n status: \"blocked\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n if (judgment.verdict === \"skipped\") {\n yield {\n type: \"status_change\",\n status: \"completed\",\n reason: `skipped: ${judgment.reason}`,\n };\n return {\n status: \"completed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n\n // M55 — checagem de token budget APÓS o turno (Codex: wind-down suave, para o loop).\n if (tokenBudget !== undefined && tokensUsed >= tokenBudget) {\n yield {\n type: \"status_change\",\n status: \"budget_limited\",\n reason: `token budget (${tokenBudget}) reached: ${tokensUsed} used`,\n };\n return {\n status: \"budget_limited\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n }\n\n yield { type: \"continuation\", turn, prompt: continuationPrompt };\n }\n\n yield {\n type: \"status_change\",\n status: \"failed\",\n reason: `max turns (${maxTurns}) exhausted`,\n };\n return {\n status: \"failed\",\n turnsUsed: turn,\n tokensUsed,\n finalResponse: lastResponse || undefined,\n };\n}\n\n/**\n * Build the continuation user message for turns 2+. Keeps last response\n * preview short so we don't bloat the prompt cache.\n *\n * @internal\n */\n\nexport function composeContinuation(goal: string, lastResponse: string): string {\n // M55 — Codex-faithful continuation (ext/goal templates/goals/continuation.md): keep the FULL\n // objective intact, work from current-state evidence, and audit completion requirement-by-requirement\n // before declaring done. Improves the quality of what the judge then evaluates.\n return [\n GOAL_CONTINUATION_MARKER,\n \"Continue working toward the active goal.\",\n \"\",\n `<objective>\\n${goal}\\n</objective>`,\n \"\",\n \"Continuation behavior:\",\n \"- This goal persists across turns. Keep the full objective intact; if it cannot be finished now,\",\n \" make concrete progress toward the real requested end state and do not redefine success around a\",\n \" smaller or easier task.\",\n \"\",\n \"Work from evidence:\",\n \"- Use the current worktree and external state as authoritative. Inspect the current state before\",\n \" relying on it. Improve, replace, or remove existing work as needed to satisfy the actual objective.\",\n \"\",\n \"Completion audit:\",\n \"- Before deciding the goal is achieved, treat completion as unproven and verify it against the actual\",\n \" current state, requirement by requirement. Treat uncertain or indirect evidence as not achieved.\",\n \"\",\n `Your last response was:\\n${lastResponse.slice(-1000)}`,\n ].join(\"\\n\");\n}\n"]}
@@ -1,15 +1,211 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+
1
3
  /**
2
- * @theokit/sdk/server/auth — public barrel
4
+ * @theokit/sdk/server/auth — typed error classes
3
5
  *
4
- * Per ADR D6 (sub-path export) consumers import via `@theokit/sdk/server/auth`
5
- * NOT main `@theokit/sdk` barrel. Tree-shaking + allows breaking changes in
6
- * auth surface without affecting Agent runtime consumers.
6
+ * Plan T1.2 + v1.1 EC-1 (AuthCancelledError for OAuth provider error response RFC 6749 §4.1.2.1).
7
+ */
8
+ /**
9
+ * Thrown at `defineAuth()` time when configuration is invalid
10
+ * (e.g., duplicate provider name, invalid email shape per EC-V1-12).
11
+ */
12
+ declare class AuthConfigError extends Error {
13
+ readonly name = "AuthConfigError";
14
+ readonly code: string;
15
+ constructor(code: string, message: string);
16
+ }
17
+ /**
18
+ * Thrown at `startSignIn(providerName, ...)` or `finishSignIn(providerName, ...)`
19
+ * when the named provider is not registered in `providers[]`.
20
+ */
21
+ declare class AuthProviderNotFoundError extends Error {
22
+ readonly name = "AuthProviderNotFoundError";
23
+ readonly providerName: string;
24
+ constructor(providerName: string);
25
+ }
26
+ /**
27
+ * Thrown during OAuth callback handling for state mismatches, expired
28
+ * transactions, missing query params, or provider 4xx/5xx errors.
29
+ *
30
+ * Typed `code` field lets consumers branch on cause:
31
+ * - 'oauth_transaction_expired' — cookie tx > 10min old (per ADR D5)
32
+ * - 'oauth_state_mismatch' — query state ≠ cookie state (CSRF defense per RFC 6749 §10.12)
33
+ * - 'oauth_provider_error' — non-access_denied error in callback URL
34
+ * - 'oauth_token_exchange_failed' — provider rejected code-for-tokens swap
35
+ * - 'oauth_userinfo_failed' — userinfo endpoint returned error
36
+ * - 'oauth_missing_code_or_state' — required query params absent
37
+ */
38
+ declare class AuthCallbackError extends Error {
39
+ readonly name: string;
40
+ readonly code: string;
41
+ constructor(code: string, message?: string);
42
+ }
43
+ /**
44
+ * Per v1.1 EC-1 MUST FIX — typed subclass of AuthCallbackError for the
45
+ * specific case where user declined consent at provider screen.
46
+ *
47
+ * OAuth 2.0 RFC 6749 §4.1.2.1: provider redirects with `?error=access_denied`.
48
+ * Apps can catch this distinctly from network/server errors to render
49
+ * "Login cancelled — try again" UX instead of opaque "callback failed".
50
+ */
51
+ declare class AuthCancelledError extends AuthCallbackError {
52
+ readonly name: string;
53
+ readonly errorDescription?: string;
54
+ constructor(errorDescription?: string);
55
+ }
56
+
57
+ /**
58
+ * @theokit/sdk/server/auth — orchestrator types (Caminho C Hybrid per G11)
59
+ *
60
+ * Plan: g11-auth-architecture-implementation v1.4 (sha256 4d381020...)
61
+ * Blueprint: g11-auth-architecture-decision v1.1 (SHIPPABLE 97.9)
62
+ * AUTH-DELEGATION lock (theokit/CLAUDE.md:217-225) — these types are the
63
+ * orchestrator contract; concrete OAuth/email providers ship in opt-in
64
+ * @theokit/auth-* packages (adapters layer per ADR D11).
65
+ */
66
+
67
+ /**
68
+ * SessionManager contract (matches theokit/packages/theo/src/server/auth/session.ts:49).
69
+ * Imported as type-only — runtime depends via peerDep `theokit@>=0.2.4`.
70
+ */
71
+ interface SessionManager<TSession> {
72
+ getSession(req: IncomingMessage): Promise<TSession | null>;
73
+ createSession(res: ServerResponse, data: TSession): Promise<void>;
74
+ destroySession(res: ServerResponse): void;
75
+ rotateSession(req: IncomingMessage, res: ServerResponse): Promise<TSession | null>;
76
+ }
77
+ /**
78
+ * Per ADR D5 — OAuth transaction state stored in encrypted HttpOnly cookie
79
+ * (cookie-state pattern). Expires within 10 minutes per invariant.
80
+ */
81
+ interface OAuthTransaction {
82
+ state: string;
83
+ pkceVerifier?: string;
84
+ returnTo?: string;
85
+ createdAt: number;
86
+ expiresAt: number;
87
+ }
88
+ /**
89
+ * Per ADR D9 — provider profile types are provider-specific (not unified).
90
+ * Each @theokit/auth-* package exports its own profile shape.
91
+ * Generic param TProfile lets consumers narrow via discriminated unions on providerName.
92
+ */
93
+ interface AuthResult<TProfile, TName extends string = string> {
94
+ profile: TProfile;
95
+ providerName: TName;
96
+ rawTokens?: {
97
+ accessToken: string;
98
+ refreshToken?: string;
99
+ idToken?: string;
100
+ expiresAt?: number;
101
+ };
102
+ }
103
+ /**
104
+ * Provider contract — each @theokit/auth-* package implements this.
105
+ * Per blueprint Q5 + ADR D11 (adapters layer).
106
+ */
107
+ interface AuthProvider<TProfile, TName extends string = string> {
108
+ name: TName;
109
+ createAuthorizationURL(tx: OAuthTransaction): URL | Promise<URL>;
110
+ handleCallback(req: IncomingMessage, tx: OAuthTransaction): Promise<AuthResult<TProfile, TName>>;
111
+ }
112
+ /**
113
+ * `defineAuth(opts)` configuration shape — Caminho C (Hybrid).
114
+ * `providers` optional: empty = Caminho A escape hatch (manual signIn only).
115
+ * `onSignIn` invoked after provider callback success; returns TSession to persist.
116
+ */
117
+ interface DefineAuthOptions<TSession> {
118
+ session: SessionManager<TSession>;
119
+ providers?: AuthProvider<unknown, string>[];
120
+ onSignIn?: <TProfile>(args: {
121
+ profile: TProfile;
122
+ provider: string;
123
+ }) => Promise<TSession>;
124
+ onSignOut?: (session: TSession | null) => Promise<void> | void;
125
+ }
126
+ /**
127
+ * Returned by `defineAuth<TSession>(opts)` — 5-method orchestrator surface.
128
+ *
129
+ * - startSignIn: returns Response.redirect to provider authorization URL with state cookie
130
+ * - finishSignIn: handles provider callback; verifies state; calls onSignIn; rotates session ID
131
+ * (OWASP A07:2021 per EC-10); creates session cookie; clears transaction cookie
132
+ * - signIn: Caminho A escape hatch — skip OAuth flow; directly persist session from external profile
133
+ * - signOut: destroys session cookie + invokes onSignOut callback
134
+ * - getSession: read-only passthrough to session.getSession
135
+ */
136
+ interface AuthOrchestrator<TSession> {
137
+ startSignIn(providerName: string, req: IncomingMessage, opts?: {
138
+ returnTo?: string;
139
+ }): Promise<Response>;
140
+ finishSignIn(providerName: string, req: IncomingMessage, res: ServerResponse): Promise<{
141
+ session: TSession;
142
+ returnTo?: string;
143
+ }>;
144
+ signIn<TProfile>(profile: TProfile, providerName: string, req: IncomingMessage, res: ServerResponse): Promise<TSession>;
145
+ signOut(res: ServerResponse): void | Promise<void>;
146
+ getSession(req: IncomingMessage): Promise<TSession | null>;
147
+ }
148
+
149
+ /**
150
+ * @theokit/sdk/server/auth — encrypted OAuth transaction cookie store
151
+ *
152
+ * Per ADR D5 — cookie-state pattern (no Redis/db dependency in core).
153
+ *
154
+ * Stores OAuthTransaction (state + pkceVerifier + returnTo + expiry) in a
155
+ * single signed+encrypted HttpOnly cookie. Stateless, works in edge/serverless.
156
+ *
157
+ * Cookie name: `__Host-theo_oauth_tx` (T5.3 — RFC 6265bis prefix)
158
+ * Lifetime: 10 minutes (per D5 invariant)
159
+ * Encryption: AES-256-GCM via Node's webcrypto subtle API
160
+ *
161
+ * Note: this is a minimal in-package implementation. Production deployments
162
+ * may prefer using `theokit/server/auth/crypto`'s encrypt/decrypt helpers
163
+ * via the SessionManager's existing secret rotation chain. For T1.2 we keep
164
+ * it self-contained to avoid cross-package peer-dep complexity; T2+ may
165
+ * refactor to share SessionManager's encrypt path.
166
+ */
167
+
168
+ /**
169
+ * T5.1 — Typed error thrown when an OAuth tx-cookie secret has < 32
170
+ * bytes of entropy. Surfaces the actual byte length so operators can
171
+ * diagnose mis-configured env vars without leaking the secret itself.
172
+ *
173
+ * @public
174
+ */
175
+ declare class AuthSecretTooShortError extends Error {
176
+ readonly name = "AuthSecretTooShortError";
177
+ constructor(actualBytes: number);
178
+ }
179
+
180
+ /**
181
+ * @theokit/sdk/server/auth — defineAuth orchestrator runtime (Caminho C Hybrid)
182
+ *
183
+ * Plan T1.2 implementation per blueprint Q5 § Caminho C signatures.
184
+ * Composes existing primitives + the v1.1 EC-1/EC-2/EC-10 fixes.
185
+ */
186
+
187
+ /** SE36 — `Auth.create` replaces `defineAuth` (ADR 0015). @public */
188
+ declare class Auth {
189
+ private constructor();
190
+ static create<TSession>(opts: DefineAuthOptions<TSession>): AuthOrchestrator<TSession>;
191
+ }
192
+
193
+ /**
194
+ * @theokit/sdk/server/auth — same-origin returnTo validator
195
+ *
196
+ * Per v1.1 EC-2 MUST FIX — OWASP A01:2021 open-redirect mitigation.
197
+ *
198
+ * Without this check, attacker craft `/login?returnTo=https://evil.com` would
199
+ * cause post-login redirect to attacker domain with authenticated session cookie.
7
200
  *
8
- * T1.1 shipped types-only.
9
- * T1.2 adds runtime: defineAuth() + 4 typed error classes + validateReturnTo helper.
201
+ * Rules:
202
+ * - undefined/empty returnTo default '/'
203
+ * - protocol-relative `//evil.com` → default '/' (URL parser would resolve to baseUrl protocol)
204
+ * - absolute URL with origin ≠ baseUrl.origin → default '/' (cross-origin redirect)
205
+ * - absolute URL with origin === baseUrl.origin → keep (same-origin allowed)
206
+ * - relative path starting with '/' → keep (same-app navigation)
207
+ * - relative path not starting with '/' → default '/' (defensive)
10
208
  */
11
- export { AuthCallbackError, AuthCancelledError, AuthConfigError, AuthProviderNotFoundError, } from "./errors.js";
12
- export { AuthSecretTooShortError } from "./oauth-transaction-store.js";
13
- export { Auth } from "./orchestrator.js";
14
- export type { AuthOrchestrator, AuthProvider, AuthResult, DefineAuthOptions, OAuthTransaction, SessionManager, } from "./types.js";
15
- export { validateReturnTo } from "./validate-return-to.js";
209
+ declare function validateReturnTo(returnTo: string | undefined, baseUrl: URL): string;
210
+
211
+ export { Auth, AuthCallbackError, AuthCancelledError, AuthConfigError, type AuthOrchestrator, type AuthProvider, AuthProviderNotFoundError, type AuthResult, AuthSecretTooShortError, type DefineAuthOptions, type OAuthTransaction, type SessionManager, validateReturnTo };