@oscharko-dev/keiko-server 0.2.7 → 0.2.8

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 (41) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/deps.d.ts +1 -0
  3. package/dist/deps.d.ts.map +1 -1
  4. package/dist/files.d.ts +18 -0
  5. package/dist/files.d.ts.map +1 -1
  6. package/dist/files.js +174 -0
  7. package/dist/gateway-readiness.d.ts +6 -0
  8. package/dist/gateway-readiness.d.ts.map +1 -0
  9. package/dist/gateway-readiness.js +624 -0
  10. package/dist/grounded-qa-hybrid.d.ts.map +1 -1
  11. package/dist/grounded-qa-hybrid.js +2 -0
  12. package/dist/grounded-qa-multi-source.d.ts.map +1 -1
  13. package/dist/grounded-qa-multi-source.js +1 -0
  14. package/dist/grounded-qa.d.ts.map +1 -1
  15. package/dist/grounded-qa.js +1 -0
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
  20. package/dist/local-knowledge-grounded-qa.js +11 -2
  21. package/dist/routes.d.ts.map +1 -1
  22. package/dist/routes.js +5 -10
  23. package/dist/run-handlers.d.ts +0 -1
  24. package/dist/run-handlers.d.ts.map +1 -1
  25. package/dist/run-handlers.js +0 -217
  26. package/dist/store/db.d.ts.map +1 -1
  27. package/dist/store/db.js +2 -1
  28. package/dist/store/index.d.ts +1 -1
  29. package/dist/store/index.d.ts.map +1 -1
  30. package/dist/store/messages.d.ts +2 -1
  31. package/dist/store/messages.d.ts.map +1 -1
  32. package/dist/store/messages.js +46 -4
  33. package/dist/store/schema.d.ts +1 -1
  34. package/dist/store/schema.d.ts.map +1 -1
  35. package/dist/store/schema.js +7 -1
  36. package/dist/store/types.d.ts +3 -2
  37. package/dist/store/types.d.ts.map +1 -1
  38. package/package.json +19 -19
  39. package/dist/grounded-handoff.d.ts +0 -4
  40. package/dist/grounded-handoff.d.ts.map +0 -1
  41. package/dist/grounded-handoff.js +0 -445
@@ -1,7 +1,7 @@
1
1
  // ADR-0013 D5 — Schema v1 + migration runner via PRAGMA user_version. Forward-only, idempotent,
2
2
  // transactional. Each migration is a `.sql` string of one or more CREATE/ALTER statements; the
3
3
  // runner applies migrations whose 1-based index > current user_version.
4
- export const SCHEMA_VERSION = 5;
4
+ export const SCHEMA_VERSION = 6;
5
5
  const V1_SQL = `
6
6
  CREATE TABLE projects (
7
7
  path TEXT NOT NULL PRIMARY KEY,
@@ -184,12 +184,18 @@ CREATE INDEX idx_relationship_audit_relationship
184
184
  ON relationship_audit_entries(workspace_id, relationship_id, occurred_at)
185
185
  WHERE relationship_id IS NOT NULL;
186
186
  `;
187
+ // V6 — persist the redacted, browser-safe grounded answer projection on assistant messages.
188
+ // This stores citation metadata and context summaries only, not retrieved excerpt contents.
189
+ const V6_SQL = `
190
+ ALTER TABLE chat_messages ADD COLUMN grounded_answer_json TEXT;
191
+ `;
187
192
  const MIGRATIONS = [
188
193
  { version: 1, sql: V1_SQL },
189
194
  { version: 2, sql: V2_SQL },
190
195
  { version: 3, sql: V3_SQL },
191
196
  { version: 4, sql: V4_SQL },
192
197
  { version: 5, sql: V5_SQL },
198
+ { version: 6, sql: V6_SQL },
193
199
  ];
194
200
  function currentUserVersion(db) {
195
201
  const row = db.prepare("PRAGMA user_version").get();
@@ -1,5 +1,5 @@
1
- import type { Project, Chat, ChatConnectedScope, ChatLocalKnowledgeScope, ChatRole, WorkflowStatus, ChatMessage, CreateChatOptions, UpdateProjectPatch, UpdateChatPatch, NewChatMessage, UpdateChatMessagePatch } from "@oscharko-dev/keiko-contracts/bff-wire";
2
- export type { Project, Chat, ChatConnectedScope, ChatLocalKnowledgeScope, ChatRole, WorkflowStatus, ChatMessage, CreateChatOptions, UpdateProjectPatch, UpdateChatPatch, NewChatMessage, UpdateChatMessagePatch, };
1
+ import type { Project, Chat, ChatConnectedScope, ChatLocalKnowledgeScope, ChatRole, WorkflowStatus, ChatMessage, CreateChatOptions, GroundedAnswer, UpdateProjectPatch, UpdateChatPatch, NewChatMessage, UpdateChatMessagePatch } from "@oscharko-dev/keiko-contracts/bff-wire";
2
+ export type { Project, Chat, ChatConnectedScope, ChatLocalKnowledgeScope, ChatRole, WorkflowStatus, ChatMessage, CreateChatOptions, GroundedAnswer, UpdateProjectPatch, UpdateChatPatch, NewChatMessage, UpdateChatMessagePatch, };
3
3
  export interface UiStore {
4
4
  readonly listProjects: () => readonly Project[];
5
5
  readonly createProject: (path: string, name?: string) => Project;
@@ -15,6 +15,7 @@ export interface UiStore {
15
15
  readonly createMessage: (msg: NewChatMessage) => ChatMessage;
16
16
  readonly createMessages: (messages: readonly NewChatMessage[]) => readonly ChatMessage[];
17
17
  readonly updateMessage: (id: string, patch: UpdateChatMessagePatch) => ChatMessage;
18
+ readonly attachGroundedAnswer: (id: string, answer: GroundedAnswer) => ChatMessage;
18
19
  readonly close: () => void;
19
20
  }
20
21
  export interface UpdateChatOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,OAAO,EACP,IAAI,EACJ,kBAAkB,EAClB,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,sBAAsB,EACvB,MAAM,wCAAwC,CAAC;AAChD,YAAY,EACV,OAAO,EACP,IAAI,EACJ,kBAAkB,EAClB,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,sBAAsB,GACvB,CAAC;AAEF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,SAAS,OAAO,EAAE,CAAC;IAChD,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACjE,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IAC7E,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/C,QAAQ,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC;IAC7E,QAAQ,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE,CACnB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,iBAAiB,KACrB,IAAI,CAAC;IACV,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC/F,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,SAAS,WAAW,EAAE,CAAC;IAClF,QAAQ,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;IAClE,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,WAAW,CAAC;IAC7D,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,KAAK,SAAS,WAAW,EAAE,CAAC;IACzF,QAAQ,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,KAAK,WAAW,CAAC;IAEnF,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAC5C;AAID,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,OAAO,EACP,IAAI,EACJ,kBAAkB,EAClB,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,sBAAsB,EACvB,MAAM,wCAAwC,CAAC;AAChD,YAAY,EACV,OAAO,EACP,IAAI,EACJ,kBAAkB,EAClB,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,sBAAsB,GACvB,CAAC;AAEF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,SAAS,OAAO,EAAE,CAAC;IAChD,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACjE,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IAC7E,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/C,QAAQ,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC;IAC7E,QAAQ,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE,CACnB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,iBAAiB,KACrB,IAAI,CAAC;IACV,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC/F,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,SAAS,WAAW,EAAE,CAAC;IAClF,QAAQ,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;IAClE,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,WAAW,CAAC;IAC7D,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,KAAK,SAAS,WAAW,EAAE,CAAC;IACzF,QAAQ,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,KAAK,WAAW,CAAC;IACnF,QAAQ,CAAC,oBAAoB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,WAAW,CAAC;IAEnF,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAC5C;AAID,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscharko-dev/keiko-server",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Internal server package: local loopback BFF runtime (HTTP/SSE/WebSocket router, CSP, host check, CSRF gate, terminal, browser, files, run engine, SQLite-backed UI store) that mediates the browser presentation tier and the Node-side domain packages (ADR-0019). Not published independently.",
@@ -29,24 +29,24 @@
29
29
  "node": ">=22"
30
30
  },
31
31
  "dependencies": {
32
- "@oscharko-dev/keiko-contracts": "0.2.7",
33
- "@oscharko-dev/keiko-security": "0.2.7",
34
- "@oscharko-dev/keiko-model-gateway": "0.2.7",
35
- "@oscharko-dev/keiko-quality-intelligence": "0.2.7",
36
- "@oscharko-dev/keiko-local-knowledge": "0.2.7",
37
- "@oscharko-dev/keiko-workspace": "0.2.7",
38
- "@oscharko-dev/keiko-sandbox": "0.2.7",
39
- "@oscharko-dev/keiko-tools": "0.2.7",
40
- "@oscharko-dev/keiko-evidence": "0.2.7",
41
- "@oscharko-dev/keiko-verification": "0.2.7",
42
- "@oscharko-dev/keiko-harness": "0.2.7",
43
- "@oscharko-dev/keiko-sdk": "0.2.7",
44
- "@oscharko-dev/keiko-workflows": "0.2.7",
45
- "@oscharko-dev/keiko-memory-vault": "0.2.7",
46
- "@oscharko-dev/keiko-memory-governance": "0.2.7",
47
- "@oscharko-dev/keiko-memory-retrieval": "0.2.7",
48
- "@oscharko-dev/keiko-memory-capture": "0.2.7",
49
- "@oscharko-dev/keiko-memory-consolidation": "0.2.7",
32
+ "@oscharko-dev/keiko-contracts": "0.2.8",
33
+ "@oscharko-dev/keiko-security": "0.2.8",
34
+ "@oscharko-dev/keiko-model-gateway": "0.2.8",
35
+ "@oscharko-dev/keiko-quality-intelligence": "0.2.8",
36
+ "@oscharko-dev/keiko-local-knowledge": "0.2.8",
37
+ "@oscharko-dev/keiko-workspace": "0.2.8",
38
+ "@oscharko-dev/keiko-sandbox": "0.2.8",
39
+ "@oscharko-dev/keiko-tools": "0.2.8",
40
+ "@oscharko-dev/keiko-evidence": "0.2.8",
41
+ "@oscharko-dev/keiko-verification": "0.2.8",
42
+ "@oscharko-dev/keiko-harness": "0.2.8",
43
+ "@oscharko-dev/keiko-sdk": "0.2.8",
44
+ "@oscharko-dev/keiko-workflows": "0.2.8",
45
+ "@oscharko-dev/keiko-memory-vault": "0.2.8",
46
+ "@oscharko-dev/keiko-memory-governance": "0.2.8",
47
+ "@oscharko-dev/keiko-memory-retrieval": "0.2.8",
48
+ "@oscharko-dev/keiko-memory-capture": "0.2.8",
49
+ "@oscharko-dev/keiko-memory-consolidation": "0.2.8",
50
50
  "typescript": "^6.0.3"
51
51
  }
52
52
  }
@@ -1,4 +0,0 @@
1
- import { type UiHandlerDeps } from "./deps.js";
2
- import type { RouteContext, RouteResult } from "./routes.js";
3
- export declare function handleGroundedWorkflowHandoff(ctx: RouteContext, deps: UiHandlerDeps): Promise<RouteResult>;
4
- //# sourceMappingURL=grounded-handoff.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grounded-handoff.d.ts","sourceRoot":"","sources":["../src/grounded-handoff.ts"],"names":[],"mappings":"AAmBA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAYxE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA8hB7D,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAOtB"}
@@ -1,445 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { DEFAULT_PATCH_SCOPE_LIMITS, EXPECTED_CHECKS, WORKFLOW_HANDOFF_SCHEMA_VERSION, WORKFLOW_KINDS, validateWorkflowHandoffRequest, } from "@oscharko-dev/keiko-contracts/workflow-handoff";
3
- import { createAuditRedactor } from "@oscharko-dev/keiko-evidence";
4
- import { WorkspaceError } from "@oscharko-dev/keiko-workspace";
5
- import { currentRedactionSecrets } from "./deps.js";
6
- import { approvalTokenInputFor, contextPackStableIdForPacks, createApprovalToken, evidenceAtomIdsForPacks, readOnlyPathsForPacks, } from "./governed-workflow.js";
7
- import { lookupGroundedTurn } from "./grounded-turn-registry.js";
8
- import { startRun } from "./run-engine.js";
9
- import { parseRunRequest } from "./run-request.js";
10
- import { ActiveRunLimitError } from "./runs.js";
11
- import { errorBody } from "./routes.js";
12
- import { memoryCaptureCustomerMatchers } from "./memory-capture-policy.js";
13
- const MAX_BODY_BYTES = 256 * 1024;
14
- const RESERVED_WORKFLOW_INPUT_FIELDS = new Set([
15
- "apply",
16
- "governedHandoff",
17
- "limits",
18
- "modelId",
19
- "workflowHandoff",
20
- "workspaceRoot",
21
- ]);
22
- const VERIFY_NOOP_MODEL = {
23
- call: () => Promise.reject(new Error("verify runs must not call the model")),
24
- };
25
- class BodyTooLargeError extends Error {
26
- constructor() {
27
- super("request body too large");
28
- this.name = "BodyTooLargeError";
29
- }
30
- }
31
- function readBody(req) {
32
- return new Promise((resolve, reject) => {
33
- const chunks = [];
34
- let total = 0;
35
- let capped = false;
36
- req.on("data", (chunk) => {
37
- total += chunk.length;
38
- if (total > MAX_BODY_BYTES) {
39
- if (!capped) {
40
- capped = true;
41
- chunks.length = 0;
42
- reject(new BodyTooLargeError());
43
- req.resume();
44
- }
45
- return;
46
- }
47
- chunks.push(chunk);
48
- });
49
- req.on("end", () => {
50
- if (!capped) {
51
- resolve(Buffer.concat(chunks).toString("utf8"));
52
- }
53
- });
54
- req.on("error", reject);
55
- });
56
- }
57
- function isRecord(value) {
58
- return typeof value === "object" && value !== null && !Array.isArray(value);
59
- }
60
- function isExpectedCheck(value) {
61
- return typeof value === "string" && EXPECTED_CHECKS.includes(value);
62
- }
63
- function isWorkflowKind(value) {
64
- return typeof value === "string" && WORKFLOW_KINDS.includes(value);
65
- }
66
- function parseJsonRecord(raw) {
67
- let parsed;
68
- try {
69
- parsed = JSON.parse(raw);
70
- }
71
- catch {
72
- return { status: 400, body: errorBody("BAD_REQUEST", "Request body is not valid JSON.") };
73
- }
74
- if (!isRecord(parsed)) {
75
- return { status: 400, body: errorBody("BAD_REQUEST", "Request body must be a JSON object.") };
76
- }
77
- return parsed;
78
- }
79
- function isRouteResult(value) {
80
- return isRecord(value) && typeof value.status === "number" && "body" in value;
81
- }
82
- function badField(field, message) {
83
- return {
84
- status: 400,
85
- body: errorBody("BAD_REQUEST", `Field "${field}" ${message}.`),
86
- };
87
- }
88
- function requireString(body, field) {
89
- const value = body[field];
90
- if (typeof value !== "string" || value.length === 0) {
91
- return badField(field, "is required");
92
- }
93
- return value;
94
- }
95
- function requireNumber(body, field) {
96
- const value = body[field];
97
- if (typeof value !== "number" || !Number.isFinite(value)) {
98
- return badField(field, "must be a finite number");
99
- }
100
- return value;
101
- }
102
- function requireRecord(body, field) {
103
- const value = body[field];
104
- if (!isRecord(value)) {
105
- return badField(field, "must be an object");
106
- }
107
- return value;
108
- }
109
- function validateWorkflowInput(input) {
110
- for (const field of RESERVED_WORKFLOW_INPUT_FIELDS) {
111
- if (Object.prototype.hasOwnProperty.call(input, field)) {
112
- return badField("input", `must not include reserved workflow field "${field}"`);
113
- }
114
- }
115
- return null;
116
- }
117
- function stringArrayField(body, field) {
118
- const value = body[field];
119
- if (!Array.isArray(value)) {
120
- return badField(field, "must be an array");
121
- }
122
- const parsed = [];
123
- for (const entry of value) {
124
- if (typeof entry !== "string" || entry.trim().length === 0) {
125
- return badField(field, "must contain non-empty strings");
126
- }
127
- parsed.push(entry);
128
- }
129
- return parsed;
130
- }
131
- function defaultExpectedChecks(workflowKind) {
132
- if (workflowKind === "unit-test-generation") {
133
- return ["tests"];
134
- }
135
- return ["verify"];
136
- }
137
- function parseExpectedChecks(body, workflowKind) {
138
- if (body.expectedChecks === undefined) {
139
- return defaultExpectedChecks(workflowKind);
140
- }
141
- const parsed = stringArrayField(body, "expectedChecks");
142
- if (isRouteResult(parsed)) {
143
- return parsed;
144
- }
145
- if (!parsed.every(isExpectedCheck)) {
146
- return {
147
- status: 400,
148
- body: errorBody("BAD_REQUEST", 'Field "expectedChecks" contains an unknown check.'),
149
- };
150
- }
151
- return parsed;
152
- }
153
- function parseWorkflowKindField(body) {
154
- const workflowKind = requireString(body, "workflowKind");
155
- if (isRouteResult(workflowKind)) {
156
- return workflowKind;
157
- }
158
- return isWorkflowKind(workflowKind)
159
- ? workflowKind
160
- : { status: 400, body: errorBody("BAD_REQUEST", 'Field "workflowKind" is invalid.') };
161
- }
162
- function parseUnknowns(body) {
163
- return body.unknowns === undefined ? [] : stringArrayField(body, "unknowns");
164
- }
165
- function parseRequiredFields(body) {
166
- const workflowKind = parseWorkflowKindField(body);
167
- if (isRouteResult(workflowKind)) {
168
- return workflowKind;
169
- }
170
- const assistantMessageId = requireString(body, "assistantMessageId");
171
- if (isRouteResult(assistantMessageId)) {
172
- return assistantMessageId;
173
- }
174
- const chatId = requireString(body, "chatId");
175
- if (isRouteResult(chatId)) {
176
- return chatId;
177
- }
178
- const modelId = requireString(body, "modelId");
179
- if (isRouteResult(modelId)) {
180
- return modelId;
181
- }
182
- const input = requireRecord(body, "input");
183
- if (isRouteResult(input)) {
184
- return input;
185
- }
186
- const inputError = validateWorkflowInput(input);
187
- if (inputError !== null) {
188
- return inputError;
189
- }
190
- return { assistantMessageId, chatId, modelId, workflowKind, input };
191
- }
192
- function parseBody(raw) {
193
- const body = parseJsonRecord(raw);
194
- if (isRouteResult(body)) {
195
- return body;
196
- }
197
- const required = parseRequiredFields(body);
198
- if (isRouteResult(required)) {
199
- return required;
200
- }
201
- const editablePaths = stringArrayField(body, "editablePaths");
202
- if (isRouteResult(editablePaths)) {
203
- return editablePaths;
204
- }
205
- const unknowns = parseUnknowns(body);
206
- if (isRouteResult(unknowns)) {
207
- return unknowns;
208
- }
209
- const requestedAtMs = requireNumber(body, "requestedAtMs");
210
- if (isRouteResult(requestedAtMs)) {
211
- return requestedAtMs;
212
- }
213
- const expectedChecks = parseExpectedChecks(body, required.workflowKind);
214
- if (isRouteResult(expectedChecks)) {
215
- return expectedChecks;
216
- }
217
- return {
218
- ...required,
219
- editablePaths,
220
- expectedChecks,
221
- unknowns,
222
- requestedAtMs,
223
- };
224
- }
225
- function workspaceRootForTurn(record) {
226
- const roots = new Set(record?.packs.map((pack) => pack.scope.workspaceRoot) ?? []);
227
- if (roots.size !== 1) {
228
- return {
229
- status: 409,
230
- body: errorBody("BAD_REQUEST", "Grounded handoff requires evidence from a single connected workspace root."),
231
- };
232
- }
233
- const root = [...roots][0];
234
- if (typeof root !== "string" || root.length === 0) {
235
- return {
236
- status: 404,
237
- body: errorBody("NOT_FOUND", "Grounded handoff context is no longer available."),
238
- };
239
- }
240
- return root;
241
- }
242
- function runRequestFor(workflowKind, modelId, workspaceRoot, input) {
243
- const parsed = parseRunRequest(JSON.stringify({
244
- ...(workflowKind === "verification" ? { taskType: "verify" } : { workflowId: workflowKind }),
245
- modelId,
246
- input: { ...input, workspaceRoot },
247
- }));
248
- if ("code" in parsed) {
249
- return { status: 400, body: errorBody(parsed.code, parsed.message) };
250
- }
251
- return parsed;
252
- }
253
- function summaryDiscriminator(workflowKind) {
254
- if (workflowKind === "verification") {
255
- return { workflowId: undefined, taskType: "verify" };
256
- }
257
- return { workflowId: workflowKind, taskType: undefined };
258
- }
259
- function workflowLabel(workflowKind) {
260
- if (workflowKind === "unit-test-generation") {
261
- return "grounded unit-test generation";
262
- }
263
- if (workflowKind === "bug-investigation") {
264
- return "grounded bug investigation";
265
- }
266
- return "grounded verification";
267
- }
268
- function persistGroundedHandoffMessages(deps, chatId, workflowKind, runId) {
269
- const now = Date.now();
270
- const discriminator = summaryDiscriminator(workflowKind);
271
- const label = workflowLabel(workflowKind);
272
- const [user, summary] = deps.store.createMessages([
273
- {
274
- chatId,
275
- role: "user",
276
- content: `Requested ${label}.`,
277
- timestamp: now,
278
- runId: undefined,
279
- workflowId: undefined,
280
- workflowStatus: undefined,
281
- shortResult: undefined,
282
- taskType: undefined,
283
- },
284
- {
285
- chatId,
286
- role: "system",
287
- content: `Launched ${label}.`,
288
- timestamp: now + 1,
289
- runId,
290
- workflowId: discriminator.workflowId,
291
- workflowStatus: "running",
292
- shortResult: undefined,
293
- taskType: discriminator.taskType,
294
- },
295
- ]);
296
- if (user === undefined || summary === undefined) {
297
- throw new Error("createMessages returned fewer rows than expected");
298
- }
299
- return [user, summary];
300
- }
301
- function resolveRunModel(request, deps) {
302
- return request.kind === "verify" ? VERIFY_NOOP_MODEL : deps.modelPortFactory(request.modelId);
303
- }
304
- function engineContextFor(deps, request, model) {
305
- return {
306
- request,
307
- model,
308
- registry: deps.registry,
309
- evidence: {
310
- store: deps.evidenceStore,
311
- env: deps.env,
312
- additionalSecrets: currentRedactionSecrets(deps),
313
- },
314
- memoryVault: deps.memoryVault,
315
- memoryAuditRedactString: createAuditRedactor({ additionalSecrets: currentRedactionSecrets(deps) }, deps.env),
316
- memoryCustomerIdentifierMatchers: memoryCaptureCustomerMatchers(deps),
317
- };
318
- }
319
- function markSummaryFailed(deps, message, shortResult) {
320
- try {
321
- deps.store.updateMessage(message.id, { workflowStatus: "failed", shortResult });
322
- }
323
- catch {
324
- // best-effort compensation only
325
- }
326
- }
327
- function mapRunStartError(error) {
328
- if (error instanceof ActiveRunLimitError) {
329
- return { status: 429, body: errorBody("TOO_MANY_RUNS", "The active run limit is reached.") };
330
- }
331
- if (error instanceof WorkspaceError) {
332
- return {
333
- status: 400,
334
- body: errorBody("WORKSPACE_UNAVAILABLE", "The selected workspace could not be prepared: no recognized project workspace marker was found, or the target file could not be read."),
335
- };
336
- }
337
- throw error;
338
- }
339
- function buildGovernedHandoffRequest(body, record) {
340
- const patchScope = {
341
- schemaVersion: WORKFLOW_HANDOFF_SCHEMA_VERSION,
342
- editablePaths: body.editablePaths,
343
- readOnlyPaths: readOnlyPathsForPacks(record.packs, body.editablePaths),
344
- evidenceAtomIds: evidenceAtomIdsForPacks(record.packs),
345
- limits: DEFAULT_PATCH_SCOPE_LIMITS,
346
- expectedChecks: body.expectedChecks,
347
- unknowns: body.unknowns,
348
- };
349
- const draft = {
350
- schemaVersion: WORKFLOW_HANDOFF_SCHEMA_VERSION,
351
- contextPackStableId: contextPackStableIdForPacks(record.packs),
352
- workflowKind: body.workflowKind,
353
- patchScope,
354
- requestedAtMs: body.requestedAtMs,
355
- userApprovalToken: "0".repeat(64),
356
- };
357
- const request = {
358
- ...draft,
359
- userApprovalToken: createApprovalToken(approvalTokenInputFor(draft)),
360
- };
361
- const validation = validateWorkflowHandoffRequest(request);
362
- if (!validation.ok) {
363
- return {
364
- status: 400,
365
- body: errorBody("BAD_REQUEST", validation.reasons.join("; ")),
366
- };
367
- }
368
- return request;
369
- }
370
- async function parseGroundedHandoffBody(req) {
371
- try {
372
- return parseBody(await readBody(req));
373
- }
374
- catch (error) {
375
- if (error instanceof BodyTooLargeError) {
376
- return {
377
- status: 413,
378
- body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
379
- };
380
- }
381
- throw error;
382
- }
383
- }
384
- function resolveGroundedHandoffLaunch(body, deps) {
385
- const record = lookupGroundedTurn(body.assistantMessageId);
386
- if (record === undefined) {
387
- return {
388
- status: 404,
389
- body: errorBody("NOT_FOUND", "Grounded handoff context is no longer available."),
390
- };
391
- }
392
- if (record.chatId !== body.chatId) {
393
- return {
394
- status: 404,
395
- body: errorBody("NOT_FOUND", "Grounded handoff context is no longer available."),
396
- };
397
- }
398
- const workspaceRoot = workspaceRootForTurn(record);
399
- if (isRouteResult(workspaceRoot)) {
400
- return workspaceRoot;
401
- }
402
- const governedHandoff = buildGovernedHandoffRequest(body, record);
403
- if (isRouteResult(governedHandoff)) {
404
- return governedHandoff;
405
- }
406
- const baseRequest = runRequestFor(body.workflowKind, body.modelId, workspaceRoot, body.input);
407
- if (isRouteResult(baseRequest)) {
408
- return baseRequest;
409
- }
410
- const request = {
411
- ...baseRequest,
412
- governedHandoff,
413
- governedHandoffSourceGroundedRunId: record.evidenceRunId,
414
- };
415
- const model = resolveRunModel(request, deps);
416
- if (model === undefined) {
417
- return { status: 400, body: errorBody("NO_MODEL", "No model provider is configured.") };
418
- }
419
- return { chatId: record.chatId, workflowKind: body.workflowKind, request, model };
420
- }
421
- function startGroundedWorkflowRun(deps, launch) {
422
- const runId = randomUUID();
423
- const messages = persistGroundedHandoffMessages(deps, launch.chatId, launch.workflowKind, runId);
424
- try {
425
- const run = startRun(engineContextFor(deps, launch.request, launch.model), deps.redactor, {
426
- runId,
427
- });
428
- return {
429
- status: 202,
430
- body: { run, messages },
431
- };
432
- }
433
- catch (error) {
434
- markSummaryFailed(deps, messages[1], "Run could not be started.");
435
- return mapRunStartError(error);
436
- }
437
- }
438
- export async function handleGroundedWorkflowHandoff(ctx, deps) {
439
- const body = await parseGroundedHandoffBody(ctx.req);
440
- if (isRouteResult(body)) {
441
- return body;
442
- }
443
- const launch = resolveGroundedHandoffLaunch(body, deps);
444
- return isRouteResult(launch) ? launch : startGroundedWorkflowRun(deps, launch);
445
- }