@tangle-network/agent-app 0.60.207 → 0.60.209

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 (46) hide show
  1. package/dist/assistant/index.js +1 -1
  2. package/dist/chat-routes/index.js +6 -2
  3. package/dist/chat-routes/index.js.map +1 -1
  4. package/dist/{chunk-65SDSB6D.js → chunk-7LMHBLFH.js} +19 -2
  5. package/dist/chunk-7LMHBLFH.js.map +1 -0
  6. package/dist/{chunk-JDEGS53O.js → chunk-LRW36WCF.js} +25 -2
  7. package/dist/chunk-LRW36WCF.js.map +1 -0
  8. package/dist/{chunk-2CBRCREL.js → chunk-R75P63W5.js} +2 -2
  9. package/dist/intakes/drizzle.js +114 -6
  10. package/dist/intakes/drizzle.js.map +1 -1
  11. package/dist/intakes/index.d.ts +2 -6
  12. package/dist/intakes/index.js +6 -8
  13. package/dist/intakes/index.js.map +1 -1
  14. package/dist/intakes/model.d.ts +1 -2
  15. package/dist/public-consultation/index.d.ts +2 -2
  16. package/dist/runtime/index.js +2 -2
  17. package/dist/runtime/openai-stream.d.ts +7 -0
  18. package/dist/runtime/router-turn.d.ts +14 -1
  19. package/dist/runtime/router-turn.js +1 -1
  20. package/dist/stories/panels/fixtures.d.ts +3 -29
  21. package/dist/web-react/index.js +3 -3
  22. package/package.json +1 -16
  23. package/dist/IntakeInterview-OKT6DCBN.js +0 -8
  24. package/dist/IntakeInterview-OKT6DCBN.js.map +0 -1
  25. package/dist/chunk-65SDSB6D.js.map +0 -1
  26. package/dist/chunk-I2CA5N7V.js +0 -254
  27. package/dist/chunk-I2CA5N7V.js.map +0 -1
  28. package/dist/chunk-JDEGS53O.js.map +0 -1
  29. package/dist/chunk-VWAEWOWK.js +0 -124
  30. package/dist/chunk-VWAEWOWK.js.map +0 -1
  31. package/dist/chunk-YZ6KQULN.js +0 -30
  32. package/dist/chunk-YZ6KQULN.js.map +0 -1
  33. package/dist/intakes/api.d.ts +0 -54
  34. package/dist/intakes/api.js +0 -63
  35. package/dist/intakes/api.js.map +0 -1
  36. package/dist/intakes-react/components/BrandMark.d.ts +0 -10
  37. package/dist/intakes-react/components/IntakeInterview.d.ts +0 -15
  38. package/dist/intakes-react/components/index.d.ts +0 -1
  39. package/dist/intakes-react/contracts.d.ts +0 -42
  40. package/dist/intakes-react/index.d.ts +0 -14
  41. package/dist/intakes-react/index.js +0 -8
  42. package/dist/intakes-react/index.js.map +0 -1
  43. package/dist/intakes-react/lazy.d.ts +0 -10
  44. package/dist/intakes-react/lazy.js +0 -9
  45. package/dist/intakes-react/lazy.js.map +0 -1
  46. /package/dist/{chunk-2CBRCREL.js.map → chunk-R75P63W5.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/intakes/drizzle/schema.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the intake tables. The product owns the user (and\n * optionally workspace) tables; this factory creates the intake tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics.\n *\n * Two tables, two scopes:\n * - `user_intake` — the one-time onboarding interview, keyed on `user.id`\n * (UNIQUE per user: one onboarding payload per user). ALWAYS created.\n * - `project_intake` — the structured intake attached to a workspace, keyed\n * on `workspace.id` (UNIQUE per workspace). Created ONLY when a\n * `workspaceTable` is passed.\n *\n * `workspaceTable` is OPTIONAL by design: a non-workspace app (a single-user\n * tool, tax-without-workspaces) adopts per-user onboarding alone with zero\n * teams and zero workspace concept. When omitted, `createIntakeTables` returns\n * `{ userIntake }` and no `projectIntake` — the FK to a workspace table that\n * does not exist is never created.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/intakes/drizzle` sub-subpath. The pure `./intakes` leaf imports none of\n * this, so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type IntakeParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\n/** Define options for creating intake tables including user and optional workspace references */\nexport interface CreateIntakeTablesOptions {\n /** The product's user table — user-intake rows reference `userTable.id`. */\n userTable: IntakeParentTable\n /**\n * The product's workspace table — project-intake rows reference\n * `workspaceTable.id`. OPTIONAL: omit it for a single-user / non-workspace\n * app that wants per-user onboarding only. When omitted, no `projectIntake`\n * table is created.\n */\n workspaceTable?: IntakeParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nfunction createUserIntakeTable(userTable: IntakeParentTable) {\n return sqliteTable('user_intake', {\n id: hexId(),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n /** The intake definition id the payload was collected against. */\n graphId: text('graph_id').notNull(),\n /** The IntakePayload JSON blob. */\n payload: text('payload', { mode: 'json' }).notNull(),\n /** Set when the user finishes the onboarding interview; null while open. */\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_user_intake_user').on(table.userId),\n ])\n}\n\nfunction createProjectIntakeTable(workspaceTable: IntakeParentTable) {\n return sqliteTable('project_intake', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n graphId: text('graph_id').notNull(),\n payload: text('payload', { mode: 'json' }).notNull(),\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_project_intake_workspace').on(table.workspaceId),\n index('idx_project_intake_graph').on(table.graphId),\n ])\n}\n\n/**\n * Build the intake tables wired to the product's tables. Always returns\n * `userIntake`; returns `projectIntake` only when `workspaceTable` is passed.\n * The return type carries `projectIntake?` so a consumer that passes no\n * workspace table cannot reference a table that does not exist.\n */\nexport function createIntakeTables<O extends CreateIntakeTablesOptions>(\n opts: O,\n): IntakeTables<O> {\n const userIntake = createUserIntakeTable(opts.userTable)\n const result: { userIntake: ReturnType<typeof createUserIntakeTable>; projectIntake?: ReturnType<typeof createProjectIntakeTable> } = {\n userIntake,\n }\n if (opts.workspaceTable) {\n result.projectIntake = createProjectIntakeTable(opts.workspaceTable)\n }\n return result as IntakeTables<O>\n}\n\n/** Resolve the structure and data of a user intake table based on the createUserIntakeTable function */\nexport type UserIntakeTable = ReturnType<typeof createUserIntakeTable>\n/** Resolve the structure and data of the project intake table from the factory function */\nexport type ProjectIntakeTable = ReturnType<typeof createProjectIntakeTable>\n\n/**\n * The tables returned for a given options shape: `projectIntake` is present in\n * the type exactly when `workspaceTable` was provided.\n */\nexport type IntakeTables<O extends CreateIntakeTablesOptions> =\n O extends { workspaceTable: IntakeParentTable }\n ? { userIntake: UserIntakeTable; projectIntake: ProjectIntakeTable }\n : { userIntake: UserIntakeTable; projectIntake?: ProjectIntakeTable }\n\n/** The union table shape, for code that handles either scope generically. */\nexport type AnyIntakeTables = { userIntake: UserIntakeTable; projectIntake?: ProjectIntakeTable }\n\n/** Infer and represent a selected row from the UserIntakeTable data structure */\nexport type UserIntakeRow = UserIntakeTable['$inferSelect']\n/** Resolve the selected data structure for a project intake table row */\nexport type ProjectIntakeRow = ProjectIntakeTable['$inferSelect']\n"],"mappings":";;;;;;;;;AAwBA,SAAS,WAAW;AACpB,SAAS,OAAO,SAAS,aAAa,MAAM,mBAAmB;AAmB/D,IAAM,QAAQ,MAAM,KAAK,IAAI,EAAE,WAAW,EAAE,QAAQ,iCAAiC;AAErF,IAAM,YAAY,MAAM,QAAQ,cAAc,EAAE,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,kBAAkB;AAEzG,IAAM,YAAY,MAAM,QAAQ,cAAc,EAAE,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,kBAAkB;AAEzG,SAAS,sBAAsB,WAA8B;AAC3D,SAAO,YAAY,eAAe;AAAA,IAChC,IAAI,MAAM;AAAA,IACV,QAAQ,KAAK,SAAS,EAAE,QAAQ,EAAE,WAAW,MAAM,UAAU,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA;AAAA,IAExF,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA;AAAA,IAElC,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,QAAQ;AAAA;AAAA,IAEnD,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAAA,IAC1D,WAAW,UAAU;AAAA,IACrB,WAAW,UAAU;AAAA,EACvB,GAAG,CAAC,UAAU;AAAA,IACZ,YAAY,uBAAuB,EAAE,GAAG,MAAM,MAAM;AAAA,EACtD,CAAC;AACH;AAEA,SAAS,yBAAyB,gBAAmC;AACnE,SAAO,YAAY,kBAAkB;AAAA,IACnC,IAAI,MAAM;AAAA,IACV,aAAa,KAAK,cAAc,EAAE,QAAQ,EAAE,WAAW,MAAM,eAAe,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IACvG,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,QAAQ;AAAA,IACnD,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAAA,IAC1D,WAAW,UAAU;AAAA,IACrB,WAAW,UAAU;AAAA,EACvB,GAAG,CAAC,UAAU;AAAA,IACZ,YAAY,+BAA+B,EAAE,GAAG,MAAM,WAAW;AAAA,IACjE,MAAM,0BAA0B,EAAE,GAAG,MAAM,OAAO;AAAA,EACpD,CAAC;AACH;AAQO,SAAS,mBACd,MACiB;AACjB,QAAM,aAAa,sBAAsB,KAAK,SAAS;AACvD,QAAM,SAAgI;AAAA,IACpI;AAAA,EACF;AACA,MAAI,KAAK,gBAAgB;AACvB,WAAO,gBAAgB,yBAAyB,KAAK,cAAc;AAAA,EACrE;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/intakes/drizzle/schema.ts","../../src/intakes/drizzle/store.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the intake tables. The product owns the user (and\n * optionally workspace) tables; this factory creates the intake tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics.\n *\n * Two tables, two scopes:\n * - `user_intake` — the one-time onboarding interview, keyed on `user.id`\n * (UNIQUE per user: one onboarding payload per user). ALWAYS created.\n * - `project_intake` — the structured intake attached to a workspace, keyed\n * on `workspace.id` (UNIQUE per workspace). Created ONLY when a\n * `workspaceTable` is passed.\n *\n * `workspaceTable` is OPTIONAL by design: a non-workspace app (a single-user\n * tool, tax-without-workspaces) adopts per-user onboarding alone with zero\n * teams and zero workspace concept. When omitted, `createIntakeTables` returns\n * `{ userIntake }` and no `projectIntake` — the FK to a workspace table that\n * does not exist is never created.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/intakes/drizzle` sub-subpath. The pure `./intakes` leaf imports none of\n * this, so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type IntakeParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\n/** Define options for creating intake tables including user and optional workspace references */\nexport interface CreateIntakeTablesOptions {\n /** The product's user table — user-intake rows reference `userTable.id`. */\n userTable: IntakeParentTable\n /**\n * The product's workspace table — project-intake rows reference\n * `workspaceTable.id`. OPTIONAL: omit it for a single-user / non-workspace\n * app that wants per-user onboarding only. When omitted, no `projectIntake`\n * table is created.\n */\n workspaceTable?: IntakeParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nfunction createUserIntakeTable(userTable: IntakeParentTable) {\n return sqliteTable('user_intake', {\n id: hexId(),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n /** The intake definition id the payload was collected against. */\n graphId: text('graph_id').notNull(),\n /** The IntakePayload JSON blob. */\n payload: text('payload', { mode: 'json' }).notNull(),\n /** Set when the user finishes the onboarding interview; null while open. */\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_user_intake_user').on(table.userId),\n ])\n}\n\nfunction createProjectIntakeTable(workspaceTable: IntakeParentTable) {\n return sqliteTable('project_intake', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n graphId: text('graph_id').notNull(),\n payload: text('payload', { mode: 'json' }).notNull(),\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_project_intake_workspace').on(table.workspaceId),\n index('idx_project_intake_graph').on(table.graphId),\n ])\n}\n\n/**\n * Build the intake tables wired to the product's tables. Always returns\n * `userIntake`; returns `projectIntake` only when `workspaceTable` is passed.\n * The return type carries `projectIntake?` so a consumer that passes no\n * workspace table cannot reference a table that does not exist.\n */\nexport function createIntakeTables<O extends CreateIntakeTablesOptions>(\n opts: O,\n): IntakeTables<O> {\n const userIntake = createUserIntakeTable(opts.userTable)\n const result: { userIntake: ReturnType<typeof createUserIntakeTable>; projectIntake?: ReturnType<typeof createProjectIntakeTable> } = {\n userIntake,\n }\n if (opts.workspaceTable) {\n result.projectIntake = createProjectIntakeTable(opts.workspaceTable)\n }\n return result as IntakeTables<O>\n}\n\n/** Resolve the structure and data of a user intake table based on the createUserIntakeTable function */\nexport type UserIntakeTable = ReturnType<typeof createUserIntakeTable>\n/** Resolve the structure and data of the project intake table from the factory function */\nexport type ProjectIntakeTable = ReturnType<typeof createProjectIntakeTable>\n\n/**\n * The tables returned for a given options shape: `projectIntake` is present in\n * the type exactly when `workspaceTable` was provided.\n */\nexport type IntakeTables<O extends CreateIntakeTablesOptions> =\n O extends { workspaceTable: IntakeParentTable }\n ? { userIntake: UserIntakeTable; projectIntake: ProjectIntakeTable }\n : { userIntake: UserIntakeTable; projectIntake?: ProjectIntakeTable }\n\n/** The union table shape, for code that handles either scope generically. */\nexport type AnyIntakeTables = { userIntake: UserIntakeTable; projectIntake?: ProjectIntakeTable }\n\n/** Infer and represent a selected row from the UserIntakeTable data structure */\nexport type UserIntakeRow = UserIntakeTable['$inferSelect']\n/** Resolve the selected data structure for a project intake table row */\nexport type ProjectIntakeRow = ProjectIntakeTable['$inferSelect']\n","/**\n * Drizzle-backed store over the tables from `createIntakeTables`. One store\n * builder per scope: `createUserIntakeStore` (keyed on userId) and\n * `createProjectIntakeStore` (keyed on workspaceId). Each returns the same\n * three-method surface — `get` / `save` / `complete` — so the api handlers and\n * the UI talk to one shape regardless of scope.\n *\n * Works against any SQLite drizzle driver (better-sqlite3, D1, libsql): the\n * builders are awaited, never `.run()`/`.all()`, so sync and async drivers\n * behave identically.\n *\n * Validation is fail-loud and pure (from the `./intakes` leaf). `save` runs\n * `validateAnswer` before writing, so an invalid answer can never enter the\n * payload; `complete` runs `payloadComplete` and refuses to stamp an\n * incomplete (or stale-graph) intake — it throws a typed `IntakeError` rather\n * than silently writing a half-finished onboarding state.\n *\n * `getProjectIntakeStore` pins `workspaceId` in every WHERE clause; RBAC runs\n * in the route before the store is built, but the scope key is enforced here\n * too, so a leaked id can never read or write across the boundary.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { eq } from 'drizzle-orm'\nimport type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'\nimport {\n type IntakeAnswerValue,\n type IntakeGraph,\n getQuestion,\n validateAnswer,\n} from '../model'\nimport {\n type IntakePayload,\n emptyPayload,\n markComplete,\n payloadComplete,\n withAnswer,\n} from '../completion'\nimport type { ProjectIntakeTable, UserIntakeTable } from './schema'\n\n/** Any SQLite drizzle database — `any` erases driver-specific generics so\n * better-sqlite3, D1, and libsql handles all fit. */\nexport type IntakeDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>\n\n/** A loaded intake: the payload plus whether it is complete against the graph. */\nexport interface IntakeState {\n payload: IntakePayload\n completed: boolean\n completedAt: Date | null\n}\n\n/** Define error codes representing specific intake validation failures */\nexport type IntakeErrorCode = 'invalid-answer' | 'unknown-question' | 'incomplete' | 'stale-graph'\n\n/** Thrown by store mutations on a refused write — callers map it to a 4xx. */\nexport class IntakeError extends Error {\n readonly code: IntakeErrorCode\n constructor(code: IntakeErrorCode, message: string) {\n super(message)\n this.name = 'IntakeError'\n this.code = code\n }\n}\n\n/** The three-method store surface, identical for both scopes. */\nexport interface IntakeStore {\n /** Load the current intake, or seed an empty payload when none exists yet. */\n get(): Promise<IntakeState>\n /** Validate and persist one answer; returns the updated state. */\n save(questionId: string, value: IntakeAnswerValue): Promise<IntakeState>\n /** Stamp the intake complete; throws IntakeError when not yet completable. */\n complete(): Promise<IntakeState>\n}\n\ninterface IntakeTableShape {\n id: any\n graphId: any\n payload: any\n completedAt: any\n updatedAt: any\n}\n\ninterface ScopedStoreOptions {\n db: IntakeDatabase\n graph: IntakeGraph\n table: IntakeTableShape\n /** The scope column (userIntake.userId or projectIntake.workspaceId). */\n scopeColumn: any\n scopeValue: string\n /** Extra columns to set on insert (the scope key). */\n insertScope: Record<string, unknown>\n}\n\nfunction createScopedStore(opts: ScopedStoreOptions): IntakeStore {\n const { db, graph, table, scopeColumn, scopeValue, insertScope } = opts\n\n async function loadRow() {\n const [row] = await db\n .select()\n .from(table as any)\n .where(eq(scopeColumn, scopeValue))\n .limit(1)\n return (row ?? null) as\n | { id: string; graphId: string; payload: unknown; completedAt: Date | number | null }\n | null\n }\n\n function toState(\n row: { graphId: string; payload: unknown; completedAt: Date | number | null } | null,\n ): IntakeState {\n if (!row) {\n return { payload: emptyPayload(graph), completed: false, completedAt: null }\n }\n const payload = normalizePayload(row.payload, row.graphId)\n const completedAt = toDate(row.completedAt)\n return {\n payload,\n completed: completedAt != null && payloadComplete(graph, payload),\n completedAt,\n }\n }\n\n async function get(): Promise<IntakeState> {\n return toState(await loadRow())\n }\n\n async function upsertPayload(payload: IntakePayload, completedAt: Date | null) {\n const existing = await loadRow()\n if (existing) {\n await db\n .update(table as any)\n .set({ graphId: payload.graphId, payload, completedAt, updatedAt: new Date() })\n .where(eq(scopeColumn, scopeValue))\n return\n }\n await db.insert(table as any).values({\n ...insertScope,\n graphId: payload.graphId,\n payload,\n completedAt,\n })\n }\n\n async function save(questionId: string, value: IntakeAnswerValue): Promise<IntakeState> {\n const question = getQuestion(graph, questionId)\n if (!question) throw new IntakeError('unknown-question', `No question '${questionId}' in intake '${graph.id}'`)\n const validity = validateAnswer(question, value)\n if (!validity.ok) {\n throw new IntakeError('invalid-answer', `Answer to '${questionId}' rejected: ${validity.reason}`)\n }\n\n const current = await get()\n const next = withAnswer(current.payload, questionId, value)\n await upsertPayload(next, current.completedAt)\n return toState({ graphId: next.graphId, payload: next, completedAt: current.completedAt })\n }\n\n async function complete(): Promise<IntakeState> {\n const current = await get()\n if (current.payload.graphId !== graph.id) {\n throw new IntakeError('stale-graph', `Intake payload was collected against '${current.payload.graphId}', not '${graph.id}'`)\n }\n if (!payloadComplete(graph, current.payload)) {\n throw new IntakeError('incomplete', `Intake '${graph.id}' has unanswered required questions`)\n }\n const completedPayload = markComplete(current.payload)\n const completedAt = new Date(completedPayload.completedAt!)\n await upsertPayload(completedPayload, completedAt)\n return { payload: completedPayload, completed: true, completedAt }\n }\n\n return { get, save, complete }\n}\n\n/** Define options required to create a user intake store for onboarding data collection */\nexport interface CreateUserIntakeStoreOptions {\n db: IntakeDatabase\n /** The user-intake table from createIntakeTables. */\n table: UserIntakeTable\n /** The intake definition this store collects answers against. */\n graph: IntakeGraph\n /** The user whose onboarding this is. */\n userId: string\n}\n\n/** Build the per-user onboarding store, scoped to one userId. */\nexport function createUserIntakeStore(opts: CreateUserIntakeStoreOptions): IntakeStore {\n return createScopedStore({\n db: opts.db,\n graph: opts.graph,\n table: opts.table as unknown as IntakeTableShape,\n scopeColumn: opts.table.userId,\n scopeValue: opts.userId,\n insertScope: { userId: opts.userId },\n })\n}\n\n/** Define options required to create a project intake store including database, table, graph, and workspace ID */\nexport interface CreateProjectIntakeStoreOptions {\n db: IntakeDatabase\n /** The project-intake table from createIntakeTables (workspace scope). */\n table: ProjectIntakeTable\n graph: IntakeGraph\n /** The workspace this intake is attached to. */\n workspaceId: string\n}\n\n/** Build the per-project store, scoped to one workspaceId. */\nexport function createProjectIntakeStore(opts: CreateProjectIntakeStoreOptions): IntakeStore {\n return createScopedStore({\n db: opts.db,\n graph: opts.graph,\n table: opts.table as unknown as IntakeTableShape,\n scopeColumn: opts.table.workspaceId,\n scopeValue: opts.workspaceId,\n insertScope: { workspaceId: opts.workspaceId },\n })\n}\n\nfunction normalizePayload(raw: unknown, graphId: string): IntakePayload {\n if (raw && typeof raw === 'object' && 'answers' in (raw as Record<string, unknown>)) {\n const candidate = raw as Partial<IntakePayload>\n return {\n graphId: candidate.graphId ?? graphId,\n answers: candidate.answers ?? {},\n ...(candidate.completedAt ? { completedAt: candidate.completedAt } : {}),\n }\n }\n return { graphId, answers: {} }\n}\n\nfunction toDate(value: Date | number | null | undefined): Date | null {\n if (value == null) return null\n return value instanceof Date ? value : new Date(Number(value) * (Number(value) < 1e12 ? 1000 : 1))\n}\n"],"mappings":"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","names":[]}
@@ -15,13 +15,9 @@
15
15
  * - `@tangle-network/agent-app/intakes/drizzle` — table factory
16
16
  * (`createIntakeTables`, `workspaceTable` OPTIONAL) + store (imports
17
17
  * `drizzle-orm`).
18
- * - `@tangle-network/agent-app/intakes/api` — framework-neutral
19
- * get-current / save-answer / complete handlers (imports `drizzle-orm`).
20
- * - `@tangle-network/agent-app/intakes-react` — one-question-at-a-time
21
- * interview UI (optional `react` peer).
22
18
  *
23
- * NEVER re-exported from the package root: `drizzle-orm` and `react` are
24
- * optional peers, and the whole point is that the tax stays clean.
19
+ * NEVER re-exported from the package root: `drizzle-orm` is an optional peer,
20
+ * and the whole point is that the tax stays clean.
25
21
  */
26
22
  export * from './model';
27
23
  export * from './completion';
@@ -1,19 +1,17 @@
1
1
  import {
2
2
  emptyPayload,
3
- markComplete,
4
- payloadComplete,
5
- payloadIsStale,
6
- withAnswer
7
- } from "../chunk-YZ6KQULN.js";
8
- import {
9
3
  getQuestion,
10
4
  hasAnswer,
11
5
  intakeProgress,
12
6
  isComplete,
7
+ markComplete,
13
8
  nextQuestion,
9
+ payloadComplete,
10
+ payloadIsStale,
14
11
  reachableQuestions,
15
- validateAnswer
16
- } from "../chunk-JDEGS53O.js";
12
+ validateAnswer,
13
+ withAnswer
14
+ } from "../chunk-LRW36WCF.js";
17
15
 
18
16
  // src/intakes/context-sufficiency.ts
19
17
  function presentValue(value) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/intakes/context-sufficiency.ts"],"sourcesContent":["/**\n * Pure context-sufficiency floor + conversational-gather scaffold — the\n * product-agnostic core of a CONVERSATIONAL intake. Where the question-graph\n * model (`./model`) runs a one-question-at-a-time interview, this leaf judges\n * whether an agent has gathered ENOUGH context to act, and renders the prompt\n * directive that tells the agent to fold the remaining gaps into its work\n * instead of running a form.\n *\n * Zero dependencies: no drizzle, no env, no react, no I/O, no product reads.\n * A product declares WHICH facts matter (`ContextFactSpec`), and its own\n * adapter resolves those facts + named substrate flags from whatever substrate\n * it owns (`ResolvedContextSignals`); this leaf only does the deterministic\n * combine and the wording. That separation is what makes the framework opt-in\n * and tree-shakeable, and what lets gtm/tax/legal/insurance share one core.\n *\n * Readiness is a two-part floor: SCOPE (every required fact has a value) AND\n * SUBSTRATE (at least one named substrate flag is true). Scope alone is not\n * enough — knowing the goal without any durable thing to act on is still\n * not-ready; substrate alone is not enough either. The product's adapter\n * decides what counts as a fact and what counts as substrate.\n */\n\n/** One fact the product treats as known context once it has a value. */\nexport interface ContextFact {\n /** Stable key the resolved-signals map is keyed on. */\n key: string\n /** Human label shown in the prompt's known/missing lists. */\n label: string\n /** When true, this fact must have a value for SCOPE to be met. */\n required?: boolean\n /** How the agent should gather this fact conversationally, if missing. */\n gatherHint?: string\n}\n\n/**\n * The product's declaration of what context matters: the facts that make up\n * scope, plus optional tool hints appended verbatim to the gather prompt (e.g.\n * a product passes the command that extracts a brand from a URL). Pure data —\n * the product supplies labels, hints, and tool lines; the framework never\n * names a product-specific concept itself.\n */\nexport interface ContextFactSpec {\n facts: ContextFact[]\n /** Lines appended after the gather directive — e.g. product tool pointers. */\n toolHints?: string[]\n}\n\n/**\n * What a product's adapter resolves from its own substrate, ready to combine.\n * `facts` carries the resolved VALUE per fact key (undefined/empty = not\n * known); `substrate` carries named boolean flags (e.g.\n * `{ brandConfirmed, configHasContext, coreKnowledgePresent }`) — any one true\n * satisfies the substrate half of the floor.\n */\nexport interface ResolvedContextSignals {\n facts: Record<string, string | undefined>\n substrate: Record<string, boolean>\n}\n\n/** A resolved fact that has a value — surfaced to the prompt as known context. */\nexport interface KnownFact {\n key: string\n label: string\n value: string\n}\n\n/** A required fact with no value — surfaced to the prompt as a gap to close. */\nexport interface MissingFact {\n key: string\n label: string\n gatherHint?: string\n}\n\n/** The deterministic verdict over the resolved signals. */\nexport interface ContextSufficiency {\n /** True when the floor is met: `hasScope && hasSubstrate`. */\n ready: boolean\n /** Every REQUIRED fact has a non-empty value. */\n hasScope: boolean\n /** At least one named substrate flag is true. */\n hasSubstrate: boolean\n /** Facts (required or not) that have a value. */\n knownFacts: KnownFact[]\n /** Required facts that have no value yet. */\n missingFacts: MissingFact[]\n /** The substrate flags as resolved, passed through for the prompt/caller. */\n substrate: Record<string, boolean>\n}\n\n/** Trim a fact value to a present string, or undefined when blank/absent. */\nfunction presentValue(value: string | undefined): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/**\n * Combine a fact spec with resolved signals into the readiness verdict. Pure,\n * deterministic, never throws — a missing fact key or an empty substrate map\n * reads as not-ready, not an error, so a fresh scope is honestly not-ready\n * rather than crashing the caller.\n *\n * SCOPE = every `required` fact resolves to a non-empty value.\n * SUBSTRATE = any value in `signals.substrate` is true.\n * READY = SCOPE && SUBSTRATE.\n *\n * `knownFacts` lists every fact (required or optional) that resolved to a\n * value, in spec declaration order; `missingFacts` lists every REQUIRED fact\n * that did not — optional facts never appear as missing because they never gate\n * scope.\n */\nexport function computeContextSufficiency(\n spec: ContextFactSpec,\n signals: ResolvedContextSignals,\n): ContextSufficiency {\n const facts = spec.facts ?? []\n const resolvedFacts = signals.facts ?? {}\n const substrate = signals.substrate ?? {}\n\n const knownFacts: KnownFact[] = []\n const missingFacts: MissingFact[] = []\n let hasScope = true\n\n for (const fact of facts) {\n const value = presentValue(resolvedFacts[fact.key])\n if (value !== undefined) {\n knownFacts.push({ key: fact.key, label: fact.label, value })\n } else if (fact.required) {\n hasScope = false\n missingFacts.push({ key: fact.key, label: fact.label, gatherHint: fact.gatherHint })\n }\n }\n\n const hasSubstrate = Object.values(substrate).some(Boolean)\n\n return {\n ready: hasScope && hasSubstrate,\n hasScope,\n hasSubstrate,\n knownFacts,\n missingFacts,\n substrate,\n }\n}\n\n/** The directive that turns gaps into conversational gathering, not a form. */\nconst GATHER_DIRECTIVE =\n 'Do not run an interview. Act on the message first, then fold AT MOST one or two pointed questions into the same turn to close the highest-leverage gap. Never present a form.'\n\n/** What to say when scope is met but no durable substrate exists yet. */\nconst SUBSTRATE_DIRECTIVE =\n 'You have the scope but no durable substrate to act on yet. As you work, persist what you learn — that is what makes this context-ready.'\n\n/**\n * Render the prompt section that mirrors a conversational-gather flow:\n * - \"### Context you already have\" — the known facts, as `label: value`.\n * - \"### Context still missing — gather it while you work, not as a form\" —\n * the missing facts (with their gather hints), the act-first directive, and\n * any product tool hints appended verbatim.\n *\n * When scope is met but the floor still is not (no substrate flag), it emits\n * the substrate directive instead of a missing-facts list. Returns '' when\n * there is nothing to say (no known facts, no gaps, and ready) so a caller can\n * concatenate it unconditionally. Pure: wording is product-neutral; every\n * product-specific phrase comes from the spec's labels, gather hints, and tool\n * hints.\n */\nexport function buildContextGatherPrompt(\n spec: ContextFactSpec,\n sufficiency: ContextSufficiency,\n): string {\n const lines: string[] = []\n\n if (sufficiency.knownFacts.length > 0) {\n lines.push('### Context you already have')\n lines.push(sufficiency.knownFacts.map((f) => `- ${f.label}: ${f.value}`).join('\\n'))\n }\n\n if (sufficiency.missingFacts.length > 0) {\n if (lines.length > 0) lines.push('')\n lines.push('### Context still missing — gather it while you work, not as a form')\n const gaps = sufficiency.missingFacts\n .map((f) => (f.gatherHint ? `- ${f.label} — ${f.gatherHint}` : `- ${f.label}`))\n .join('\\n')\n lines.push(`You do NOT have:\\n${gaps}`)\n lines.push(GATHER_DIRECTIVE)\n for (const hint of spec.toolHints ?? []) {\n const trimmed = hint.trim()\n if (trimmed) lines.push(trimmed)\n }\n } else if (!sufficiency.ready) {\n if (lines.length > 0) lines.push('')\n lines.push(SUBSTRATE_DIRECTIVE)\n for (const hint of spec.toolHints ?? []) {\n const trimmed = hint.trim()\n if (trimmed) lines.push(trimmed)\n }\n }\n\n if (lines.length === 0) return ''\n return `## Project Context & Sufficiency\\n${lines.join('\\n')}`\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0FA,SAAS,aAAa,OAA+C;AACnE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAiBO,SAAS,0BACd,MACA,SACoB;AACpB,QAAM,QAAQ,KAAK,SAAS,CAAC;AAC7B,QAAM,gBAAgB,QAAQ,SAAS,CAAC;AACxC,QAAM,YAAY,QAAQ,aAAa,CAAC;AAExC,QAAM,aAA0B,CAAC;AACjC,QAAM,eAA8B,CAAC;AACrC,MAAI,WAAW;AAEf,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,aAAa,cAAc,KAAK,GAAG,CAAC;AAClD,QAAI,UAAU,QAAW;AACvB,iBAAW,KAAK,EAAE,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO,MAAM,CAAC;AAAA,IAC7D,WAAW,KAAK,UAAU;AACxB,iBAAW;AACX,mBAAa,KAAK,EAAE,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO,YAAY,KAAK,WAAW,CAAC;AAAA,IACrF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,OAAO,SAAS,EAAE,KAAK,OAAO;AAE1D,SAAO;AAAA,IACL,OAAO,YAAY;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,IAAM,mBACJ;AAGF,IAAM,sBACJ;AAgBK,SAAS,yBACd,MACA,aACQ;AACR,QAAM,QAAkB,CAAC;AAEzB,MAAI,YAAY,WAAW,SAAS,GAAG;AACrC,UAAM,KAAK,8BAA8B;AACzC,UAAM,KAAK,YAAY,WAAW,IAAI,CAAC,MAAM,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACrF;AAEA,MAAI,YAAY,aAAa,SAAS,GAAG;AACvC,QAAI,MAAM,SAAS,EAAG,OAAM,KAAK,EAAE;AACnC,UAAM,KAAK,0EAAqE;AAChF,UAAM,OAAO,YAAY,aACtB,IAAI,CAAC,MAAO,EAAE,aAAa,KAAK,EAAE,KAAK,WAAM,EAAE,UAAU,KAAK,KAAK,EAAE,KAAK,EAAG,EAC7E,KAAK,IAAI;AACZ,UAAM,KAAK;AAAA,EAAqB,IAAI,EAAE;AACtC,UAAM,KAAK,gBAAgB;AAC3B,eAAW,QAAQ,KAAK,aAAa,CAAC,GAAG;AACvC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,QAAS,OAAM,KAAK,OAAO;AAAA,IACjC;AAAA,EACF,WAAW,CAAC,YAAY,OAAO;AAC7B,QAAI,MAAM,SAAS,EAAG,OAAM,KAAK,EAAE;AACnC,UAAM,KAAK,mBAAmB;AAC9B,eAAW,QAAQ,KAAK,aAAa,CAAC,GAAG;AACvC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,QAAS,OAAM,KAAK,OAAO;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO;AAAA,EAAqC,MAAM,KAAK,IAAI,CAAC;AAC9D;","names":[]}
1
+ {"version":3,"sources":["../../src/intakes/context-sufficiency.ts"],"sourcesContent":["/**\n * Pure context-sufficiency floor + conversational-gather scaffold — the\n * product-agnostic core of a CONVERSATIONAL intake. Where the question-graph\n * model (`./model`) runs a one-question-at-a-time interview, this leaf judges\n * whether an agent has gathered ENOUGH context to act, and renders the prompt\n * directive that tells the agent to fold the remaining gaps into its work\n * instead of running a form.\n *\n * Zero dependencies: no drizzle, no env, no react, no I/O, no product reads.\n * A product declares WHICH facts matter (`ContextFactSpec`), and its own\n * adapter resolves those facts + named substrate flags from whatever substrate\n * it owns (`ResolvedContextSignals`); this leaf only does the deterministic\n * combine and the wording. That separation is what makes the framework opt-in\n * and tree-shakeable, and what lets gtm/tax/legal/insurance share one core.\n *\n * Readiness is a two-part floor: SCOPE (every required fact has a value) AND\n * SUBSTRATE (at least one named substrate flag is true). Scope alone is not\n * enough — knowing the goal without any durable thing to act on is still\n * not-ready; substrate alone is not enough either. The product's adapter\n * decides what counts as a fact and what counts as substrate.\n */\n\n/** One fact the product treats as known context once it has a value. */\nexport interface ContextFact {\n /** Stable key the resolved-signals map is keyed on. */\n key: string\n /** Human label shown in the prompt's known/missing lists. */\n label: string\n /** When true, this fact must have a value for SCOPE to be met. */\n required?: boolean\n /** How the agent should gather this fact conversationally, if missing. */\n gatherHint?: string\n}\n\n/**\n * The product's declaration of what context matters: the facts that make up\n * scope, plus optional tool hints appended verbatim to the gather prompt (e.g.\n * a product passes the command that extracts a brand from a URL). Pure data —\n * the product supplies labels, hints, and tool lines; the framework never\n * names a product-specific concept itself.\n */\nexport interface ContextFactSpec {\n facts: ContextFact[]\n /** Lines appended after the gather directive — e.g. product tool pointers. */\n toolHints?: string[]\n}\n\n/**\n * What a product's adapter resolves from its own substrate, ready to combine.\n * `facts` carries the resolved VALUE per fact key (undefined/empty = not\n * known); `substrate` carries named boolean flags (e.g.\n * `{ brandConfirmed, configHasContext, coreKnowledgePresent }`) — any one true\n * satisfies the substrate half of the floor.\n */\nexport interface ResolvedContextSignals {\n facts: Record<string, string | undefined>\n substrate: Record<string, boolean>\n}\n\n/** A resolved fact that has a value — surfaced to the prompt as known context. */\nexport interface KnownFact {\n key: string\n label: string\n value: string\n}\n\n/** A required fact with no value — surfaced to the prompt as a gap to close. */\nexport interface MissingFact {\n key: string\n label: string\n gatherHint?: string\n}\n\n/** The deterministic verdict over the resolved signals. */\nexport interface ContextSufficiency {\n /** True when the floor is met: `hasScope && hasSubstrate`. */\n ready: boolean\n /** Every REQUIRED fact has a non-empty value. */\n hasScope: boolean\n /** At least one named substrate flag is true. */\n hasSubstrate: boolean\n /** Facts (required or not) that have a value. */\n knownFacts: KnownFact[]\n /** Required facts that have no value yet. */\n missingFacts: MissingFact[]\n /** The substrate flags as resolved, passed through for the prompt/caller. */\n substrate: Record<string, boolean>\n}\n\n/** Trim a fact value to a present string, or undefined when blank/absent. */\nfunction presentValue(value: string | undefined): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/**\n * Combine a fact spec with resolved signals into the readiness verdict. Pure,\n * deterministic, never throws — a missing fact key or an empty substrate map\n * reads as not-ready, not an error, so a fresh scope is honestly not-ready\n * rather than crashing the caller.\n *\n * SCOPE = every `required` fact resolves to a non-empty value.\n * SUBSTRATE = any value in `signals.substrate` is true.\n * READY = SCOPE && SUBSTRATE.\n *\n * `knownFacts` lists every fact (required or optional) that resolved to a\n * value, in spec declaration order; `missingFacts` lists every REQUIRED fact\n * that did not — optional facts never appear as missing because they never gate\n * scope.\n */\nexport function computeContextSufficiency(\n spec: ContextFactSpec,\n signals: ResolvedContextSignals,\n): ContextSufficiency {\n const facts = spec.facts ?? []\n const resolvedFacts = signals.facts ?? {}\n const substrate = signals.substrate ?? {}\n\n const knownFacts: KnownFact[] = []\n const missingFacts: MissingFact[] = []\n let hasScope = true\n\n for (const fact of facts) {\n const value = presentValue(resolvedFacts[fact.key])\n if (value !== undefined) {\n knownFacts.push({ key: fact.key, label: fact.label, value })\n } else if (fact.required) {\n hasScope = false\n missingFacts.push({ key: fact.key, label: fact.label, gatherHint: fact.gatherHint })\n }\n }\n\n const hasSubstrate = Object.values(substrate).some(Boolean)\n\n return {\n ready: hasScope && hasSubstrate,\n hasScope,\n hasSubstrate,\n knownFacts,\n missingFacts,\n substrate,\n }\n}\n\n/** The directive that turns gaps into conversational gathering, not a form. */\nconst GATHER_DIRECTIVE =\n 'Do not run an interview. Act on the message first, then fold AT MOST one or two pointed questions into the same turn to close the highest-leverage gap. Never present a form.'\n\n/** What to say when scope is met but no durable substrate exists yet. */\nconst SUBSTRATE_DIRECTIVE =\n 'You have the scope but no durable substrate to act on yet. As you work, persist what you learn — that is what makes this context-ready.'\n\n/**\n * Render the prompt section that mirrors a conversational-gather flow:\n * - \"### Context you already have\" — the known facts, as `label: value`.\n * - \"### Context still missing — gather it while you work, not as a form\" —\n * the missing facts (with their gather hints), the act-first directive, and\n * any product tool hints appended verbatim.\n *\n * When scope is met but the floor still is not (no substrate flag), it emits\n * the substrate directive instead of a missing-facts list. Returns '' when\n * there is nothing to say (no known facts, no gaps, and ready) so a caller can\n * concatenate it unconditionally. Pure: wording is product-neutral; every\n * product-specific phrase comes from the spec's labels, gather hints, and tool\n * hints.\n */\nexport function buildContextGatherPrompt(\n spec: ContextFactSpec,\n sufficiency: ContextSufficiency,\n): string {\n const lines: string[] = []\n\n if (sufficiency.knownFacts.length > 0) {\n lines.push('### Context you already have')\n lines.push(sufficiency.knownFacts.map((f) => `- ${f.label}: ${f.value}`).join('\\n'))\n }\n\n if (sufficiency.missingFacts.length > 0) {\n if (lines.length > 0) lines.push('')\n lines.push('### Context still missing — gather it while you work, not as a form')\n const gaps = sufficiency.missingFacts\n .map((f) => (f.gatherHint ? `- ${f.label} — ${f.gatherHint}` : `- ${f.label}`))\n .join('\\n')\n lines.push(`You do NOT have:\\n${gaps}`)\n lines.push(GATHER_DIRECTIVE)\n for (const hint of spec.toolHints ?? []) {\n const trimmed = hint.trim()\n if (trimmed) lines.push(trimmed)\n }\n } else if (!sufficiency.ready) {\n if (lines.length > 0) lines.push('')\n lines.push(SUBSTRATE_DIRECTIVE)\n for (const hint of spec.toolHints ?? []) {\n const trimmed = hint.trim()\n if (trimmed) lines.push(trimmed)\n }\n }\n\n if (lines.length === 0) return ''\n return `## Project Context & Sufficiency\\n${lines.join('\\n')}`\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA0FA,SAAS,aAAa,OAA+C;AACnE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAiBO,SAAS,0BACd,MACA,SACoB;AACpB,QAAM,QAAQ,KAAK,SAAS,CAAC;AAC7B,QAAM,gBAAgB,QAAQ,SAAS,CAAC;AACxC,QAAM,YAAY,QAAQ,aAAa,CAAC;AAExC,QAAM,aAA0B,CAAC;AACjC,QAAM,eAA8B,CAAC;AACrC,MAAI,WAAW;AAEf,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,aAAa,cAAc,KAAK,GAAG,CAAC;AAClD,QAAI,UAAU,QAAW;AACvB,iBAAW,KAAK,EAAE,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO,MAAM,CAAC;AAAA,IAC7D,WAAW,KAAK,UAAU;AACxB,iBAAW;AACX,mBAAa,KAAK,EAAE,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO,YAAY,KAAK,WAAW,CAAC;AAAA,IACrF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,OAAO,SAAS,EAAE,KAAK,OAAO;AAE1D,SAAO;AAAA,IACL,OAAO,YAAY;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,IAAM,mBACJ;AAGF,IAAM,sBACJ;AAgBK,SAAS,yBACd,MACA,aACQ;AACR,QAAM,QAAkB,CAAC;AAEzB,MAAI,YAAY,WAAW,SAAS,GAAG;AACrC,UAAM,KAAK,8BAA8B;AACzC,UAAM,KAAK,YAAY,WAAW,IAAI,CAAC,MAAM,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACrF;AAEA,MAAI,YAAY,aAAa,SAAS,GAAG;AACvC,QAAI,MAAM,SAAS,EAAG,OAAM,KAAK,EAAE;AACnC,UAAM,KAAK,0EAAqE;AAChF,UAAM,OAAO,YAAY,aACtB,IAAI,CAAC,MAAO,EAAE,aAAa,KAAK,EAAE,KAAK,WAAM,EAAE,UAAU,KAAK,KAAK,EAAE,KAAK,EAAG,EAC7E,KAAK,IAAI;AACZ,UAAM,KAAK;AAAA,EAAqB,IAAI,EAAE;AACtC,UAAM,KAAK,gBAAgB;AAC3B,eAAW,QAAQ,KAAK,aAAa,CAAC,GAAG;AACvC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,QAAS,OAAM,KAAK,OAAO;AAAA,IACjC;AAAA,EACF,WAAW,CAAC,YAAY,OAAO;AAC7B,QAAI,MAAM,SAAS,EAAG,OAAM,KAAK,EAAE;AACnC,UAAM,KAAK,mBAAmB;AAC9B,eAAW,QAAQ,KAAK,aAAa,CAAC,GAAG;AACvC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,QAAS,OAAM,KAAK,OAAO;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO;AAAA,EAAqC,MAAM,KAAK,IAAI,CAAC;AAC9D;","names":[]}
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Pure intake MODEL — the question-graph and the answer/completion algebra a
3
3
  * one-question-at-a-time interview runs on. Zero dependencies: no drizzle, no
4
- * env, no react, no I/O. The DB layer (`./intakes/drizzle`), the handlers
5
- * (`./intakes/api`) and the React surface (`./intakes-react`) all build on
4
+ * env, no react, no I/O. The DB layer (`./intakes/drizzle`) builds on
6
5
  * these functions; this leaf imports nothing back, so a consumer can pull just
7
6
  * the intake math.
8
7
  *
@@ -102,8 +102,8 @@ interface GatewayConsultationContext {
102
102
  /** Compose gateway admission, scoped knowledge and the maintained persisted chat route. */
103
103
  export declare function createPublicConsultation(options: PublicConsultationOptions): {
104
104
  authorizeConsumer(agent: ConsultationAgent, consumer: ConsultationConsumer): Promise<{
105
- allow: true;
106
105
  reason?: undefined;
106
+ allow: true;
107
107
  code?: undefined;
108
108
  } | {
109
109
  allow: false;
@@ -121,9 +121,9 @@ export declare function createPublicConsultation(options: PublicConsultationOpti
121
121
  executionBudget?: ChatTurnExecutionLimits;
122
122
  maxOutputTokens?: number;
123
123
  }) => Promise<{
124
- start?: undefined;
125
124
  status: 'unsupported';
126
125
  reason: string;
126
+ start?: undefined;
127
127
  } | {
128
128
  reason?: undefined;
129
129
  status: 'prepared';
@@ -2,7 +2,7 @@ import {
2
2
  ProtectedModelSettlementError,
3
3
  createRouterProtectedModelPort,
4
4
  runProtectedRouterTurn
5
- } from "../chunk-2CBRCREL.js";
5
+ } from "../chunk-R75P63W5.js";
6
6
  import {
7
7
  DURABLE_HOP_BUDGET_MS,
8
8
  asksForLanguage,
@@ -24,7 +24,7 @@ import {
24
24
  turnKnowledgeInstructions,
25
25
  workspaceKnowledgeForTurn,
26
26
  workspaceTurnCheckpoints
27
- } from "../chunk-65SDSB6D.js";
27
+ } from "../chunk-7LMHBLFH.js";
28
28
  import "../chunk-J63KUZBE.js";
29
29
  import {
30
30
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
@@ -43,6 +43,13 @@ export interface OpenAIStreamChunk {
43
43
  };
44
44
  finish_reason?: string | null;
45
45
  }>;
46
+ /** A failure the service reports inside the stream (the Tangle Router's
47
+ * `empty_completion`, `reasoning_budget_exhausted`, `stream_idle_timeout`, …). */
48
+ error?: {
49
+ code?: string | null;
50
+ message?: string | null;
51
+ type?: string | null;
52
+ };
46
53
  /** Token accounting (requires `stream_options.include_usage`). Cumulative for
47
54
  * the call: the Tangle Router can send it on more than one chunk. */
48
55
  usage?: {
@@ -66,6 +66,11 @@ export type RouterTurnEvent = {
66
66
  | {
67
67
  type: 'reasoning';
68
68
  delta: string;
69
+ }
70
+ /** The safe repeat is starting a fresh model call on `model`; what the failed call reasoned is finished. */
71
+ | {
72
+ type: 'repeat';
73
+ model: string;
69
74
  } | {
70
75
  type: 'tool';
71
76
  callId: string;
@@ -100,7 +105,8 @@ export type RouterTurnEvent = {
100
105
  type: 'tool_event';
101
106
  } & RouterTurnToolEvent);
102
107
  export interface RouterTurnFailure {
103
- /** `model_http_<status>`, `model_unavailable`, `model_empty`, `turn_<stopReason>`, `turn_aborted`,
108
+ /** `model_http_<status>`, `model_stream_<code>` (an error frame inside the stream, e.g. `model_stream_empty_completion`),
109
+ * `model_unavailable`, `model_empty`, `turn_<stopReason>`, `turn_aborted`,
104
110
  * `turn_tool_limit`, `router_unconfigured` or `placement_fallback_refused`; under a protected grant also the `grant_*` and
105
111
  * `settlement_*` codes of `./protected-router-turn`. */
106
112
  code: string;
@@ -236,6 +242,13 @@ export interface RouterTurnOptions {
236
242
  stream?: boolean;
237
243
  /** Sent as `reasoning_effort`. */
238
244
  reasoningEffort?: string;
245
+ /**
246
+ * The caller shows the model's reasoning as it streams (`onEvent` `reasoning` events). With `onEvent`, a
247
+ * streamed chat call and the safe repeat on, the Router then releases the stream at the first reasoning frame
248
+ * instead of holding it for the first word, and returns Gemini's thought summaries (gateway
249
+ * `streamReasoningImmediately`). Default false; `createRouterTurnProducer` turns it on.
250
+ */
251
+ showReasoning?: boolean;
239
252
  /** Sent as `max_completion_tokens`: one model call's visible and reasoning tokens together. */
240
253
  maxCompletionTokens?: number;
241
254
  /** At most this many tool calls run. The next one is refused before it runs and the turn ends
@@ -11,7 +11,7 @@ import {
11
11
  turnKnowledgeInstructions,
12
12
  workspaceKnowledgeForTurn,
13
13
  workspaceTurnCheckpoints
14
- } from "../chunk-65SDSB6D.js";
14
+ } from "../chunk-7LMHBLFH.js";
15
15
  import "../chunk-J63KUZBE.js";
16
16
  import "../chunk-D6UJEOZQ.js";
17
17
  import "../chunk-AMUXRAUO.js";
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Fixtures for the panels area (teams / intakes / work-product). Local to this
2
+ * Fixtures for the panels area (teams / work-product). Local to this
3
3
  * area: the shared `src/stories/fixtures` barrel covers chat, catalog, canvas,
4
4
  * and sequences — none of which these surfaces consume.
5
5
  *
6
6
  * Shapes mirror the API seams: `MemberView` / `InvitationView` are what
7
- * `teams/members-api` and `teams/invitations-api` return, `IntakeView` is what
8
- * `intakes/api` returns, and `WorkProductRecord` is the durable review row.
7
+ * `teams/members-api` and `teams/invitations-api` return, and `WorkProductRecord`
8
+ * is the durable review row.
9
9
  */
10
- import type { IntakeAnswers } from '../../intakes/model';
11
- import type { IntakeView } from '../../intakes-react';
12
10
  import type { InviteAcceptDetails, InvitationView, MemberView } from '../../teams-react';
13
11
  import type { ProfileBacktestSummary, WorkProductRecord } from '../../work-product/types';
14
12
  /** A realistic workspace roster: an explicit owner, an inherited org owner
@@ -24,30 +22,6 @@ export declare const INVITE_READY: InviteAcceptDetails;
24
22
  export declare const INVITE_EMAIL_MISMATCH: InviteAcceptDetails;
25
23
  /** Right address, unverified — the resend-verification branch. */
26
24
  export declare const INVITE_NEEDS_VERIFICATION: InviteAcceptDetails;
27
- /** Derive a view from the graph the same way `intakes/api` does — the server
28
- * re-derives next question + progress after every persisted answer. */
29
- export declare function intakeViewFor(answers: IntakeAnswers, completed?: boolean): IntakeView;
30
- /** Answer snapshots at each step of the flow, for the static step stories. */
31
- export declare const INTAKE_STEPS: {
32
- /** Nothing answered — the text question, progress 0/3. */
33
- start: {};
34
- /** Name answered — the single-select question, progress 1/3. */
35
- role: {
36
- name: string;
37
- };
38
- /** Role answered — the boolean question (optional multi-select skipped), 2/3. */
39
- briefings: {
40
- name: string;
41
- role: string;
42
- };
43
- /** All required answered — nextQuestion is null, the Finish action shows. */
44
- ready: {
45
- name: string;
46
- role: string;
47
- stack: string[];
48
- briefings: true;
49
- };
50
- };
51
25
  /** Bodies behind `history[].artifactPath`, for the version-compare seam. */
52
26
  export declare const VERSION_BODIES: Record<string, string>;
53
27
  /** A ready-for-review legal redline: baseline diff, span/model evidence,
@@ -144,6 +144,9 @@ import {
144
144
  withoutRecordGridRemoved,
145
145
  withoutRecordGridUpdate
146
146
  } from "../chunk-KNTRS7OZ.js";
147
+ import {
148
+ MENTION_PILL_CLASS
149
+ } from "../chunk-3HUFJ5LO.js";
147
150
  import {
148
151
  SessionHistoryPanel,
149
152
  formatSessionTimestamp,
@@ -151,9 +154,6 @@ import {
151
154
  useSessionActions,
152
155
  useSessionHistory
153
156
  } from "../chunk-KMIDRUUH.js";
154
- import {
155
- MENTION_PILL_CLASS
156
- } from "../chunk-3HUFJ5LO.js";
157
157
  import "../chunk-FBVLEGEG.js";
158
158
  import {
159
159
  EvidenceLineageTable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.60.207",
3
+ "version": "0.60.209",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [
@@ -592,21 +592,6 @@
592
592
  "import": "./dist/intakes/drizzle.js",
593
593
  "default": "./dist/intakes/drizzle.js"
594
594
  },
595
- "./intakes/api": {
596
- "types": "./dist/intakes/api.d.ts",
597
- "import": "./dist/intakes/api.js",
598
- "default": "./dist/intakes/api.js"
599
- },
600
- "./intakes-react": {
601
- "types": "./dist/intakes-react/index.d.ts",
602
- "import": "./dist/intakes-react/index.js",
603
- "default": "./dist/intakes-react/index.js"
604
- },
605
- "./intakes-react/lazy": {
606
- "types": "./dist/intakes-react/lazy.d.ts",
607
- "import": "./dist/intakes-react/lazy.js",
608
- "default": "./dist/intakes-react/lazy.js"
609
- },
610
595
  "./record": {
611
596
  "types": "./dist/record/index.d.ts",
612
597
  "import": "./dist/record/index.js",
@@ -1,8 +0,0 @@
1
- import {
2
- IntakeInterview
3
- } from "./chunk-I2CA5N7V.js";
4
- import "./chunk-JDEGS53O.js";
5
- export {
6
- IntakeInterview
7
- };
8
- //# sourceMappingURL=IntakeInterview-OKT6DCBN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}