@noodleseed/one 0.154.0 → 0.156.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 (154) hide show
  1. package/README.md +5 -1
  2. package/dist/assistant-embed-django-tests.d.ts +3 -0
  3. package/dist/assistant-embed-django-tests.d.ts.map +1 -0
  4. package/dist/assistant-embed-django-tests.js +140 -0
  5. package/dist/assistant-embed-django-tests.js.map +1 -0
  6. package/dist/assistant-embed-django.d.ts +4 -0
  7. package/dist/assistant-embed-django.d.ts.map +1 -0
  8. package/dist/assistant-embed-django.js +259 -0
  9. package/dist/assistant-embed-django.js.map +1 -0
  10. package/dist/assistant-embed-installation.d.ts +29 -0
  11. package/dist/assistant-embed-installation.d.ts.map +1 -0
  12. package/dist/assistant-embed-installation.js +124 -0
  13. package/dist/assistant-embed-installation.js.map +1 -0
  14. package/dist/assistant-embed-profile.d.ts +8 -0
  15. package/dist/assistant-embed-profile.d.ts.map +1 -0
  16. package/dist/assistant-embed-profile.js +150 -0
  17. package/dist/assistant-embed-profile.js.map +1 -0
  18. package/dist/assistant-embed-scaffold-template.d.ts +3 -2
  19. package/dist/assistant-embed-scaffold-template.d.ts.map +1 -1
  20. package/dist/assistant-embed-scaffold-template.js +39 -84
  21. package/dist/assistant-embed-scaffold-template.js.map +1 -1
  22. package/dist/assistant-embed-tests.d.ts +5 -0
  23. package/dist/assistant-embed-tests.d.ts.map +1 -0
  24. package/dist/assistant-embed-tests.js +106 -0
  25. package/dist/assistant-embed-tests.js.map +1 -0
  26. package/dist/assistant-embed-vue.d.ts +3 -0
  27. package/dist/assistant-embed-vue.d.ts.map +1 -0
  28. package/dist/assistant-embed-vue.js +77 -0
  29. package/dist/assistant-embed-vue.js.map +1 -0
  30. package/dist/commands/assistant-embed-ops.d.ts.map +1 -1
  31. package/dist/commands/assistant-embed-ops.js +112 -63
  32. package/dist/commands/assistant-embed-ops.js.map +1 -1
  33. package/dist/commands/author-loop.d.ts.map +1 -1
  34. package/dist/commands/author-loop.js +24 -28
  35. package/dist/commands/author-loop.js.map +1 -1
  36. package/dist/commands/deploy-first-flow.d.ts.map +1 -1
  37. package/dist/commands/deploy-first-flow.js +26 -15
  38. package/dist/commands/deploy-first-flow.js.map +1 -1
  39. package/dist/commands/deploy-preflight.d.ts +5 -0
  40. package/dist/commands/deploy-preflight.d.ts.map +1 -1
  41. package/dist/commands/deploy-preflight.js +44 -5
  42. package/dist/commands/deploy-preflight.js.map +1 -1
  43. package/dist/commands/local-readiness.d.ts +9 -1
  44. package/dist/commands/local-readiness.d.ts.map +1 -1
  45. package/dist/commands/local-readiness.js +74 -4
  46. package/dist/commands/local-readiness.js.map +1 -1
  47. package/dist/commands/project-setup.d.ts.map +1 -1
  48. package/dist/commands/project-setup.js +10 -15
  49. package/dist/commands/project-setup.js.map +1 -1
  50. package/dist/control-plane-request.d.ts +4 -0
  51. package/dist/control-plane-request.d.ts.map +1 -1
  52. package/dist/control-plane-request.js +40 -20
  53. package/dist/control-plane-request.js.map +1 -1
  54. package/dist/project-scaffold-model.d.ts +14 -0
  55. package/dist/project-scaffold-model.d.ts.map +1 -0
  56. package/dist/project-scaffold-model.js +36 -0
  57. package/dist/project-scaffold-model.js.map +1 -0
  58. package/dist/project-scaffold-templates.d.ts.map +1 -1
  59. package/dist/project-scaffold-templates.js +73 -39
  60. package/dist/project-scaffold-templates.js.map +1 -1
  61. package/dist/project-scaffold-test-template.d.ts +4 -0
  62. package/dist/project-scaffold-test-template.d.ts.map +1 -0
  63. package/dist/project-scaffold-test-template.js +89 -0
  64. package/dist/project-scaffold-test-template.js.map +1 -0
  65. package/dist/project.d.ts.map +1 -1
  66. package/dist/project.js +1 -2
  67. package/dist/project.js.map +1 -1
  68. package/dist/widget-focused-template.d.ts +1 -1
  69. package/dist/widget-focused-template.d.ts.map +1 -1
  70. package/dist/widget-focused-template.js +34 -12
  71. package/dist/widget-focused-template.js.map +1 -1
  72. package/dist/widget-scaffold-template.d.ts +1 -1
  73. package/dist/widget-scaffold-template.d.ts.map +1 -1
  74. package/dist/widget-scaffold-template.js +75 -43
  75. package/dist/widget-scaffold-template.js.map +1 -1
  76. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +18 -4
  77. package/node_modules/@noodle-borg/agent-kit/dist/skill-app-playbook-ref.js +2 -2
  78. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +9 -12
  79. package/node_modules/@noodle-borg/agent-kit/dist/skill-connect-refs.js +2 -2
  80. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-operations-ref.js +1 -1
  81. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +34 -26
  82. package/node_modules/@noodle-borg/agent-kit/dist/skill-server-playbook-ref.js +2 -2
  83. package/node_modules/@noodle-borg/agent-kit/dist/skill-verification-ref.js +2 -1
  84. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  85. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-suggestions.js +5 -3
  86. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-bounds.d.ts +36 -0
  87. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-bounds.js +52 -0
  88. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-store.d.ts +115 -0
  89. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-store.js +51 -0
  90. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-continuity-store.d.ts +26 -0
  91. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-continuity-store.js +80 -0
  92. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +1 -0
  93. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +2 -0
  94. package/node_modules/@noodle-borg/assistant-gateway/dist/model-response-values.js +11 -0
  95. package/node_modules/@noodle-borg/assistant-gateway/dist/model-responses.js +1 -10
  96. package/node_modules/@noodle-borg/assistant-gateway/dist/model-stream.js +1 -10
  97. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +3 -0
  98. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +3 -0
  99. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  100. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +41 -0
  101. package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
  102. package/node_modules/@noodle-borg/authoring/dist/recording.d.ts +1 -1
  103. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +10 -4
  104. package/node_modules/@noodle-borg/compiler/dist/manifest/branding-schema.js +86 -0
  105. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +44 -25
  106. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +24 -76
  107. package/node_modules/@noodle-borg/compiler/package.json +0 -1
  108. package/node_modules/@noodle-borg/deploy-client/dist/deploy-payload-size.js +6 -2
  109. package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +1 -1
  110. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +193 -96
  111. package/node_modules/@noodle-borg/service/package.json +1 -1
  112. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +3 -0
  113. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +3 -0
  114. package/node_modules/@noodleseed/assistant/README.md +21 -23
  115. package/node_modules/@noodleseed/assistant/package.json +1 -1
  116. package/package.json +5 -4
  117. package/dist/saas-scaffold-template.d.ts +0 -4
  118. package/dist/saas-scaffold-template.d.ts.map +0 -1
  119. package/dist/saas-scaffold-template.js +0 -56
  120. package/dist/saas-scaffold-template.js.map +0 -1
  121. package/node_modules/tailwindcss/LICENSE +0 -21
  122. package/node_modules/tailwindcss/README.md +0 -36
  123. package/node_modules/tailwindcss/dist/chunk-5JIJA4QV.mjs +0 -1
  124. package/node_modules/tailwindcss/dist/chunk-HMCCH6MG.mjs +0 -1
  125. package/node_modules/tailwindcss/dist/chunk-X4GG3EDV.mjs +0 -1
  126. package/node_modules/tailwindcss/dist/colors-C__qRT83.d.ts +0 -347
  127. package/node_modules/tailwindcss/dist/colors.d.mts +0 -347
  128. package/node_modules/tailwindcss/dist/colors.d.ts +0 -5
  129. package/node_modules/tailwindcss/dist/colors.js +0 -1
  130. package/node_modules/tailwindcss/dist/colors.mjs +0 -1
  131. package/node_modules/tailwindcss/dist/default-theme.d.mts +0 -1199
  132. package/node_modules/tailwindcss/dist/default-theme.d.ts +0 -1199
  133. package/node_modules/tailwindcss/dist/default-theme.js +0 -1
  134. package/node_modules/tailwindcss/dist/default-theme.mjs +0 -1
  135. package/node_modules/tailwindcss/dist/flatten-color-palette.d.mts +0 -6
  136. package/node_modules/tailwindcss/dist/flatten-color-palette.d.ts +0 -6
  137. package/node_modules/tailwindcss/dist/flatten-color-palette.js +0 -1
  138. package/node_modules/tailwindcss/dist/flatten-color-palette.mjs +0 -1
  139. package/node_modules/tailwindcss/dist/lib.d.mts +0 -379
  140. package/node_modules/tailwindcss/dist/lib.d.ts +0 -3
  141. package/node_modules/tailwindcss/dist/lib.js +0 -38
  142. package/node_modules/tailwindcss/dist/lib.mjs +0 -38
  143. package/node_modules/tailwindcss/dist/plugin.d.mts +0 -11
  144. package/node_modules/tailwindcss/dist/plugin.d.ts +0 -134
  145. package/node_modules/tailwindcss/dist/plugin.js +0 -1
  146. package/node_modules/tailwindcss/dist/plugin.mjs +0 -1
  147. package/node_modules/tailwindcss/dist/resolve-config-B4yBzhca.d.ts +0 -29
  148. package/node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.mts +0 -190
  149. package/node_modules/tailwindcss/dist/types-DWdTiksJ.d.mts +0 -128
  150. package/node_modules/tailwindcss/index.css +0 -944
  151. package/node_modules/tailwindcss/package.json +0 -89
  152. package/node_modules/tailwindcss/preflight.css +0 -393
  153. package/node_modules/tailwindcss/theme.css +0 -510
  154. package/node_modules/tailwindcss/utilities.css +0 -1
@@ -0,0 +1,150 @@
1
+ import { embedBrowserContractTest } from './assistant-embed-tests.js';
2
+ /** Reuse preflight's explicit env aliases instead of duplicating browser configuration. */
3
+ export function embedCheckCommand(surface) {
4
+ const flags = surface === 'public'
5
+ ? ' --env-alias NOODLE_SERVICE_URL=NEXT_PUBLIC_NOODLE_SERVICE_URL --require-env NEXT_PUBLIC_NOODLE_EMBED_ID'
6
+ : surface === 'mixed'
7
+ ? ' --require-env NEXT_PUBLIC_NOODLE_EMBED_ID --require-env NEXT_PUBLIC_NOODLE_SERVICE_URL'
8
+ : '';
9
+ return `noodle assistant embed --check --surface ${surface}${flags} --json`;
10
+ }
11
+ /** Public mounting uses the existing hosted session source, never a customer proxy route. */
12
+ export function publicEmbedFiles(surface) {
13
+ const mixed = surface === 'mixed';
14
+ return {
15
+ 'test/noodle-assistant.browser.mjs': embedBrowserContractTest(),
16
+ 'components/noodle-assistant.tsx': `'use client';
17
+
18
+ import dynamic from 'next/dynamic';
19
+ ${mixed ? "import { useEffect, useRef } from 'react';\n" : ''}
20
+ const NoodleAssistant = dynamic(
21
+ () => import('@noodleseed/assistant/react').then((mod) => mod.NoodleAssistant),
22
+ { ssr: false },
23
+ );
24
+ ${mixed
25
+ ? `
26
+ export interface AssistantSignInRequest {
27
+ readonly signInTicket: string;
28
+ readonly expiresAt: string;
29
+ readonly intent: 'sign-in' | 'sign-up';
30
+ }
31
+
32
+ /** Bind the ticket to your backend's existing login transaction; never put it in a URL or storage. */
33
+ export function AssistantWidget({ principalKey, onSignInRequested }: {
34
+ /** Null while anonymous; change this stable user/tenant key whenever the signed-in account changes. */
35
+ readonly principalKey: string | null;
36
+ readonly onSignInRequested: (request: AssistantSignInRequest) => void;
37
+ }) {
38
+ const host = useRef<HTMLDivElement>(null);
39
+ useEffect(() => {
40
+ const element = host.current;
41
+ if (!element) return;
42
+ const signIn = (event: Event) => {
43
+ if (!(event instanceof CustomEvent)) return;
44
+ const detail: unknown = event.detail;
45
+ if (typeof detail !== 'object' || detail === null ||
46
+ !('signInTicket' in detail) || typeof detail.signInTicket !== 'string' ||
47
+ !('expiresAt' in detail) || typeof detail.expiresAt !== 'string' ||
48
+ !('intent' in detail) || (detail.intent !== 'sign-in' && detail.intent !== 'sign-up')) return;
49
+ onSignInRequested({ signInTicket: detail.signInTicket, expiresAt: detail.expiresAt, intent: detail.intent });
50
+ };
51
+ element.addEventListener('assistant-sign-in-requested', signIn);
52
+ return () => element.removeEventListener('assistant-sign-in-requested', signIn);
53
+ }, [onSignInRequested]);
54
+ return <div ref={host}>{principalKey === null ? <NoodleAssistant
55
+ key="anonymous"
56
+ embedId={process.env.NEXT_PUBLIC_NOODLE_EMBED_ID}
57
+ serviceUrl={process.env.NEXT_PUBLIC_NOODLE_SERVICE_URL}
58
+ theme="auto"
59
+ /> : <NoodleAssistant
60
+ key={principalKey}
61
+ sessionEndpoint="/api/assistant/session"
62
+ theme="auto"
63
+ />}</div>;
64
+ }`
65
+ : `
66
+ /** The embed ID is public configuration; the runtime owns anonymous sessions and admission. */
67
+ export function AssistantWidget() {
68
+ return <NoodleAssistant
69
+ embedId={process.env.NEXT_PUBLIC_NOODLE_EMBED_ID}
70
+ serviceUrl={process.env.NEXT_PUBLIC_NOODLE_SERVICE_URL}
71
+ theme="auto"
72
+ />;
73
+ }`}
74
+ `,
75
+ '.env.local.example': `# Public configuration only; safe in the browser bundle.
76
+ # Use the local embed ID printed by noodle dev, or the hosted ID printed by noodle deploy.
77
+ NEXT_PUBLIC_NOODLE_EMBED_ID=
78
+ NEXT_PUBLIC_NOODLE_SERVICE_URL=https://cloud.noodleseed.dev
79
+ ${mixed ? '' : 'PUBLIC_APP_ORIGIN=http://localhost:3000\n'}# Run checks with these names exported; the CLI does not load application dotenv files.
80
+ # Declare the page's exact origin in publicWebsite({ origins: [...] }).
81
+ # If the host sends CSP, allow the service in connect-src and frame-src.
82
+ # Script-tag mounts also require script-src. No customer backend config route is needed.
83
+ `,
84
+ 'NOODLE-INTEGRATION.md': embedIntegrationGuide(surface),
85
+ };
86
+ }
87
+ /** One customer handoff accompanies every installed profile and its machine-readable plan. */
88
+ export function embedIntegrationGuide(surface) {
89
+ return `# Noodle application integration
90
+
91
+ Surface: **${surface}**. Installation is not integration proof: application authorization, backend operations
92
+ and browser behavior are **unverified** until the checks below execute against your application.
93
+
94
+ ## Application-owned changes
95
+
96
+ ${surface === 'public'
97
+ ? `Set the public embed ID and service URL, then render \`AssistantWidget\`. Public sessions need no customer
98
+ backend session/config route or backend credential. Reuse existing business endpoints for capability data.
99
+ Public admission and capability allowlists are enforced by the runtime; Origin alone is not authentication.`
100
+ : `Implement \`authenticateAssistantRequest\` in \`lib/noodle-assistant-auth.ts\` using the existing login/session
101
+ and backend membership checks. It returns null for signed-out callers. Derive identity, tenant, roles,
102
+ scopes and routing server-side; never accept them from browser JSON. Preserve your application's CSRF policy.
103
+ Keep backend exchange credentials in the existing server secret manager, never in browser-prefixed variables.`}
104
+ ${surface === 'mixed'
105
+ ? `
106
+ Supply \`AssistantWidget\`'s \`principalKey\` (null while anonymous, then a stable user/tenant key) and
107
+ \`onSignInRequested\` callback. The key resets browser state on account changes; it is not authentication.
108
+ Bind \`signInTicket\` to a short-lived backend login
109
+ transaction before using the existing login or signup flow. After authentication, spend that ticket through
110
+ the generated authentication adapter by returning \`signInTicket\` with the verified user; the maintained handler
111
+ spends it and forwards the response unchanged
112
+ through the destination's same-origin session endpoint. Never put a ticket in URLs, analytics, logs or durable
113
+ browser storage. Ticket possession is not authentication or confirmation. This application-owned transaction
114
+ is unverified until your login round trip is tested; do not silently replace it with a new anonymous session.
115
+ `
116
+ : ''}
117
+
118
+ Reuse the application's existing business functions and authorization. Add a thin stable HTTPS handler only
119
+ when no suitable API exists. Keep \`server.ts\` in the Noodle project; do not duplicate business logic or create
120
+ a second login system. The customer application remains the system of record.
121
+
122
+ ## Verify before declaring readiness
123
+
124
+ 1. Install a compatible \`@noodleseed/assistant\` version in this application's package manager and build the host.
125
+ ${surface === 'public' ? '' : ' The server entry must export `createAssistantSessionHandler`. Run the supplied contract suite with your Vitest runner: `vitest run test/noodle-assistant.test.ts`. If the application has no Vitest dependency, add it using its existing package manager first. The suite exercises the generated route with synthetic identities; add real session/membership acceptance cases in your existing test harness.\n'}
126
+ 2. Configure the exact origins and operator bindings; do not put environment-specific secrets in source.
127
+ 3. With these environment names exported, run \`${embedCheckCommand(surface)}\`; this is static evidence only.
128
+ 4. ${surface === 'public' ? 'Exercise anonymous discovery and a safe handoff, and prove private capabilities are unavailable.' : 'Test signed-out JSON 401, wrong-origin rejection, invalid JSON/content type, and successful verified identity.'}
129
+ 5. Test cross-tenant denial at the real application boundary and an authorized read plus confirmed action or handoff.
130
+ 6. Exercise the actual browser mount, CSP, one turn and any linked App in an authorized local or sandbox environment.
131
+
132
+ The supplied mount check runs with \`node --test test/noodle-assistant.browser.mjs\` while the application is
133
+ running at \`PUBLIC_APP_ORIGIN\`. It uses Playwright and Chromium; reuse the application's browser-test setup,
134
+ or add Playwright as a development dependency and install Chromium first. Reuse existing login fixtures for
135
+ protected pages. This check does not submit a turn, confirm an action or claim production readiness.
136
+
137
+ Missing credentials, a test identity, a reachable sandbox or an authorized action remain unverified. Mock tests
138
+ prove only their fixtures. Do not call a production mutation just to complete a check.
139
+
140
+ ## Ownership and updates
141
+
142
+ These files are customer-owned after installation. Preview with \`noodle assistant embed --surface ${surface} --dry-run --json\`;
143
+ reruns create missing files and report conflicts without overwriting modifications. Review changes before using
144
+ \`--force\`. Keep custom session/business code and secrets; update library versions through the normal dependency
145
+ workflow and rerun the integration checks. Deployment, configuration writes and publication need their own authority.
146
+
147
+ Guide: https://docs.noodleseed.dev/docs/guides/embedded-assistant
148
+ `;
149
+ }
150
+ //# sourceMappingURL=assistant-embed-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-embed-profile.js","sourceRoot":"","sources":["../src/assistant-embed-profile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAAC,OAAqB;IACrD,MAAM,KAAK,GACT,OAAO,KAAK,QAAQ;QAClB,CAAC,CAAC,0GAA0G;QAC5G,CAAC,CAAC,OAAO,KAAK,OAAO;YACnB,CAAC,CAAC,yFAAyF;YAC3F,CAAC,CAAC,EAAE,CAAC;IACX,OAAO,4CAA4C,OAAO,GAAG,KAAK,SAAS,CAAC;AAC9E,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,gBAAgB,CAAC,OAA2B;IAC1D,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,CAAC;IAClC,OAAO;QACL,mCAAmC,EAAE,wBAAwB,EAAE;QAC/D,iCAAiC,EAAE;;;EAGrC,KAAK,CAAC,CAAC,CAAC,8CAA8C,CAAC,CAAC,CAAC,EAAE;;;;;EAM3D,KAAK;YACH,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuCJ;YACE,CAAC,CAAC;;;;;;;;EASN;CACC;QACG,oBAAoB,EAAE;;;;EAIxB,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,2CAA2C;;;;CAIzD;QACG,uBAAuB,EAAE,qBAAqB,CAAC,OAAO,CAAC;KACxD,CAAC;AACJ,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,qBAAqB,CAAC,OAAqB;IACzD,OAAO;;aAEI,OAAO;;;;;EAMlB,OAAO,KAAK,QAAQ;QAClB,CAAC,CAAC;;4GAEsG;QACxG,CAAC,CAAC;;;8GAIN;EAEE,OAAO,KAAK,OAAO;QACjB,CAAC,CAAC;;;;;;;;;;CAUL;QACG,CAAC,CAAC,EACN;;;;;;;;;EASE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,sZAAsZ;;kDAElY,iBAAiB,CAAC,OAAO,CAAC;KACvE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,kGAAkG,CAAC,CAAC,CAAC,gHAAgH;;;;;;;;;;;;;;qGAc5I,OAAO;;;;;;CAM3G,CAAC;AACF,CAAC"}
@@ -5,7 +5,8 @@
5
5
  * reconciles per file and never overwrites user edits without `--force`
6
6
  * (docs/roadmap/embedded-assistant-hardening.md S8).
7
7
  */
8
- export type EmbedFramework = 'nextjs';
8
+ export type EmbedFramework = 'nextjs' | 'django-vue';
9
+ export type EmbedSurface = 'authenticated' | 'public' | 'mixed';
9
10
  /** Return the canonical files for embedding an assistant in one supported host framework. */
10
- export declare function embedScaffoldFiles(framework: EmbedFramework): Record<string, string>;
11
+ export declare function embedScaffoldFiles(framework: EmbedFramework, surface?: EmbedSurface): Record<string, string>;
11
12
  //# sourceMappingURL=assistant-embed-scaffold-template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-embed-scaffold-template.d.ts","sourceRoot":"","sources":["../src/assistant-embed-scaffold-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEtC,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqIpF"}
1
+ {"version":3,"file":"assistant-embed-scaffold-template.d.ts","sourceRoot":"","sources":["../src/assistant-embed-scaffold-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,YAAY,CAAC;AACrD,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEhE,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,OAAO,GAAE,YAA8B,GACtC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqFxB"}
@@ -5,99 +5,41 @@
5
5
  * reconciles per file and never overwrites user edits without `--force`
6
6
  * (docs/roadmap/embedded-assistant-hardening.md S8).
7
7
  */
8
+ import { djangoEmbedFiles } from './assistant-embed-django.js';
9
+ import { embedIntegrationGuide, publicEmbedFiles } from './assistant-embed-profile.js';
10
+ import { embedBrowserContractTest, nextSessionContractTest } from './assistant-embed-tests.js';
8
11
  /** Return the canonical files for embedding an assistant in one supported host framework. */
9
- export function embedScaffoldFiles(framework) {
12
+ export function embedScaffoldFiles(framework, surface = 'authenticated') {
13
+ if (framework === 'django-vue') {
14
+ if (surface !== 'authenticated')
15
+ throw new Error('django-vue supports authenticated surfaces only');
16
+ return djangoEmbedFiles();
17
+ }
10
18
  if (framework !== 'nextjs')
11
19
  throw new Error(`unsupported framework: ${String(framework)}`);
12
- return {
13
- 'app/api/assistant/session/route.ts': `import { createAssistantSession } from '@noodleseed/assistant/server';
20
+ if (surface === 'public')
21
+ return publicEmbedFiles(surface);
22
+ const files = {
23
+ 'app/api/assistant/session/route.ts': `import { createAssistantSessionHandler } from '@noodleseed/assistant/server';
14
24
  import { authenticateAssistantRequest } from '../../../../lib/noodle-assistant-auth';
15
25
 
16
- type AssistantContextValue = string | number | boolean | null;
17
- type AssistantSessionRequest = {
18
- readonly context?: Readonly<Record<string, AssistantContextValue>>;
19
- };
20
-
21
- /**
22
- * Exchange the signed-in user for a short-lived assistant session.
23
- * The browser never sees NOODLE_ASSISTANT_CLIENT_SECRET or a model key.
24
- */
25
- export async function POST(request: Request) {
26
- const appOrigin = requireEnv('PUBLIC_APP_ORIGIN');
27
- if (request.headers.get('origin') !== appOrigin) {
28
- return jsonError(403, 'origin is not allowed');
29
- }
30
- if (request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json') {
31
- return jsonError(415, 'application/json required');
32
- }
33
-
34
- // This direct adapter call avoids auth middleware turning an API failure into an HTML login redirect.
35
- const identity = await authenticateAssistantRequest(request);
36
- if (identity === null) return jsonError(401, 'authentication required');
37
-
38
- let parsed: unknown;
39
- try {
40
- parsed = await request.json();
41
- } catch {
42
- return jsonError(400, 'invalid JSON');
43
- }
44
- if (!isAssistantSessionRequest(parsed)) {
45
- return jsonError(400, 'invalid request body');
46
- }
47
-
48
- const session = await createAssistantSession({
49
- serviceUrl: requireEnv('NOODLE_SERVICE_URL'),
50
- clientId: requireEnv('NOODLE_ASSISTANT_CLIENT_ID'),
51
- clientSecret: requireEnv('NOODLE_ASSISTANT_CLIENT_SECRET'),
52
- origin: appOrigin,
53
- ...identity,
54
- ...(parsed.context !== undefined ? { context: parsed.context } : {}),
55
- });
56
- // Forward the session unchanged: the widget reads endpoints/configuration from it directly.
57
- return Response.json(session, { headers: { 'Cache-Control': 'no-store' } });
58
- }
59
-
60
- /** Accept only the bounded body that the embedding backend may forward into an assistant session. */
61
- function isAssistantSessionRequest(value: unknown): value is AssistantSessionRequest {
62
- if (!isRecord(value) || Object.keys(value).some((key) => key !== 'context')) return false;
63
- if (value.context === undefined) return true;
64
- return (
65
- isRecord(value.context) &&
66
- Object.values(value.context).every(
67
- (entry) =>
68
- entry === null ||
69
- typeof entry === 'string' ||
70
- typeof entry === 'number' ||
71
- typeof entry === 'boolean',
72
- )
73
- );
74
- }
75
-
76
- /** Reject null and arrays before reading JSON object fields. */
77
- function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
78
- return typeof value === 'object' && value !== null && !Array.isArray(value);
79
- }
80
-
81
- function requireEnv(name: string): string {
82
- const value = process.env[name];
83
- if (value === undefined || value === '') throw new Error(\`\${name} is not configured\`);
84
- return value;
85
- }
86
-
87
- function jsonError(status: number, error: string): Response {
88
- return Response.json({ error }, { status, headers: { 'Cache-Control': 'no-store' } });
89
- }
26
+ // Library-owned guards, bounded parsing and exchange. Only the existing login adapter is app-specific.
27
+ // Keep these credentials server-only and preserve any stricter application CSRF middleware.
28
+ export const POST = createAssistantSessionHandler({
29
+ serviceUrl: process.env.NOODLE_SERVICE_URL,
30
+ clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID,
31
+ clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET,
32
+ origin: process.env.PUBLIC_APP_ORIGIN,
33
+ authenticate: authenticateAssistantRequest,
34
+ });
90
35
  `,
91
- 'lib/noodle-assistant-auth.ts': `import type { CreateAssistantSessionInput } from '@noodleseed/assistant/server';
36
+ 'lib/noodle-assistant-auth.ts': `import type { AssistantSessionIdentity } from '@noodleseed/assistant/server';
92
37
 
93
38
  /**
94
39
  * The only application-specific adapter in the generated integration. Replace the function body with
95
40
  * the host application's existing cookie/JWT auth and server-owned membership lookup.
96
41
  */
97
- export type NoodleAssistantIdentity = Pick<
98
- CreateAssistantSessionInput,
99
- 'user' | 'claims' | 'preferences' | 'routing'
100
- >;
42
+ export type NoodleAssistantIdentity = AssistantSessionIdentity;
101
43
 
102
44
  export async function authenticateAssistantRequest(
103
45
  _request: Request,
@@ -105,6 +47,8 @@ export async function authenticateAssistantRequest(
105
47
  // Return null for a signed-out request; the route converts it to a JSON 401. Do not redirect here.
106
48
  // Derive roles, scopes, claims, and routing only from verified backend state. In particular,
107
49
  // routing.endpoints must come from server-owned membership, never request JSON or headers.
50
+ // For mixed-mode continuation, return signInTicket only from the backend-bound login transaction;
51
+ // browser JSON cannot select it. The library preserves the continued session and automatic resume.
108
52
  //
109
53
  // The route's JSON content type plus exact-Origin check is its minimum cookie/CSRF boundary. If the
110
54
  // host requires a CSRF token header, keep that middleware and use the DOM-free client with an injected
@@ -112,6 +56,8 @@ export async function authenticateAssistantRequest(
112
56
  return null;
113
57
  }
114
58
  `,
59
+ 'test/noodle-assistant.test.ts': nextSessionContractTest(),
60
+ 'test/noodle-assistant.browser.mjs': embedBrowserContractTest(),
115
61
  'components/noodle-assistant.tsx': `'use client';
116
62
 
117
63
  import dynamic from 'next/dynamic';
@@ -123,8 +69,10 @@ const NoodleAssistant = dynamic(
123
69
  );
124
70
 
125
71
  /** Mount only inside the authenticated application surface. */
126
- export function AssistantWidget() {
127
- return <NoodleAssistant sessionEndpoint="/api/assistant/session" theme="auto" />;
72
+ export function AssistantWidget({ principalKey }: { readonly principalKey: string }) {
73
+ // Stable user/tenant key from existing application state. Remount when the account changes.
74
+ // This key clears browser state; the backend session adapter remains the identity authority.
75
+ return <NoodleAssistant key={principalKey} sessionEndpoint="/api/assistant/session" theme="auto" />;
128
76
  }
129
77
  `,
130
78
  '.env.local.example': `# Backend-only assistant credentials — from \`noodle assistant clients create\`.
@@ -140,5 +88,12 @@ PUBLIC_APP_ORIGIN=http://localhost:3000
140
88
  # noodle assistant embed --check --json
141
89
  `,
142
90
  };
91
+ return {
92
+ ...files,
93
+ ...(surface === 'mixed' ? publicEmbedFiles(surface) : {}),
94
+ '.env.local.example': files['.env.local.example'] +
95
+ (surface === 'mixed' ? publicEmbedFiles(surface)['.env.local.example'] : ''),
96
+ 'NOODLE-INTEGRATION.md': embedIntegrationGuide(surface),
97
+ };
143
98
  }
144
99
  //# sourceMappingURL=assistant-embed-scaffold-template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-embed-scaffold-template.js","sourceRoot":"","sources":["../src/assistant-embed-scaffold-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,SAAyB;IAC1D,IAAI,SAAS,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC3F,OAAO;QACL,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6EzC;QACG,8BAA8B,EAAE;;;;;;;;;;;;;;;;;;;;;;;CAuBnC;QACG,iCAAiC,EAAE;;;;;;;;;;;;;;CActC;QACG,oBAAoB,EAAE;;;;;;;;;;;CAWzB;KACE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"assistant-embed-scaffold-template.js","sourceRoot":"","sources":["../src/assistant-embed-scaffold-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACvF,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAK/F,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAChC,SAAyB,EACzB,UAAwB,eAAe;IAEvC,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,eAAe;YAC7B,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,OAAO,gBAAgB,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,SAAS,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC3F,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG;QACZ,oCAAoC,EAAE;;;;;;;;;;;;CAYzC;QACG,8BAA8B,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBnC;QACG,+BAA+B,EAAE,uBAAuB,EAAE;QAC1D,mCAAmC,EAAE,wBAAwB,EAAE;QAC/D,iCAAiC,EAAE;;;;;;;;;;;;;;;;CAgBtC;QACG,oBAAoB,EAAE;;;;;;;;;;;CAWzB;KACE,CAAC;IACF,OAAO;QACL,GAAG,KAAK;QACR,GAAG,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,oBAAoB,EAClB,KAAK,CAAC,oBAAoB,CAAC;YAC3B,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,uBAAuB,EAAE,qBAAqB,CAAC,OAAO,CAAC;KACxD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Runnable browser mount check; opening the assistant or executing a workflow is deliberately separate. */
2
+ export declare function embedBrowserContractTest(): string;
3
+ /** Executable customer-side contract tests; synthetic identities are not proof of the real login seam. */
4
+ export declare function nextSessionContractTest(): string;
5
+ //# sourceMappingURL=assistant-embed-tests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-embed-tests.d.ts","sourceRoot":"","sources":["../src/assistant-embed-tests.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,wBAAgB,wBAAwB,IAAI,MAAM,CA2BjD;AAED,0GAA0G;AAC1G,wBAAgB,uBAAuB,IAAI,MAAM,CA0EhD"}
@@ -0,0 +1,106 @@
1
+ /** Runnable browser mount check; opening the assistant or executing a workflow is deliberately separate. */
2
+ export function embedBrowserContractTest() {
3
+ return `import assert from 'node:assert/strict';
4
+ import test from 'node:test';
5
+ import { chromium } from 'playwright';
6
+
7
+ test('assistant mounts in the running customer application', { timeout: 30_000 }, async () => {
8
+ const origin = process.env.PUBLIC_APP_ORIGIN;
9
+ assert.ok(origin, 'Set PUBLIC_APP_ORIGIN to the running local or approved sandbox application.');
10
+ assert.equal(new URL(origin).origin, origin, 'PUBLIC_APP_ORIGIN must be an exact origin, without a path.');
11
+ const browser = await chromium.launch({ headless: true });
12
+ try {
13
+ // If the application requires login before this page, reuse its existing login fixture here.
14
+ // Never commit a real cookie/token or log a session. This test never submits a turn or action.
15
+ const page = await browser.newPage();
16
+ const failures = [];
17
+ page.on('pageerror', () => failures.push('browser JavaScript failed'));
18
+ await page.goto(origin);
19
+ await page.waitForFunction(() => Boolean(
20
+ customElements.get('noodle-assistant') && document.querySelector('noodle-assistant')?.shadowRoot,
21
+ ));
22
+ assert.deepEqual(failures, []);
23
+ assert.equal(await page.locator('noodle-assistant').count(), 1);
24
+ } finally { await browser.close(); }
25
+ });
26
+ // This proves mounting only. Separately prove real session/tenant rules, CSP, one read and a confirmed
27
+ // action or handoff, using approved sandbox data. Installed files and a mounted component are not readiness.
28
+ `;
29
+ }
30
+ /** Executable customer-side contract tests; synthetic identities are not proof of the real login seam. */
31
+ export function nextSessionContractTest() {
32
+ return `import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
33
+ import { authenticateAssistantRequest } from '../lib/noodle-assistant-auth';
34
+
35
+ vi.mock('../lib/noodle-assistant-auth', () => ({ authenticateAssistantRequest: vi.fn() }));
36
+
37
+ const session = {
38
+ token: 'synthetic-test-session', expiresAt: '2030-01-01T00:00:00Z',
39
+ endpoints: { turns: 'https://cloud.example/turns', toolConfirmations: 'https://cloud.example/confirmations' },
40
+ };
41
+ const upstream = vi.fn<typeof fetch>();
42
+ let POST: (request: Request) => Promise<Response>;
43
+
44
+ beforeEach(async () => {
45
+ vi.resetModules();
46
+ vi.stubEnv('PUBLIC_APP_ORIGIN', 'https://app.example');
47
+ vi.stubEnv('NOODLE_SERVICE_URL', 'https://cloud.example');
48
+ vi.stubEnv('NOODLE_ASSISTANT_CLIENT_ID', 'synthetic-client');
49
+ vi.stubEnv('NOODLE_ASSISTANT_CLIENT_SECRET', 'synthetic-test-credential');
50
+ upstream.mockReset().mockImplementation(async () => Response.json(session));
51
+ vi.stubGlobal('fetch', upstream);
52
+ vi.mocked(authenticateAssistantRequest).mockReset().mockResolvedValue(null);
53
+ ({ POST } = await import('../app/api/assistant/session/route'));
54
+ });
55
+ afterEach(() => { vi.unstubAllGlobals(); vi.unstubAllEnvs(); });
56
+
57
+ function request(body: unknown = {}, origin = 'https://app.example', contentType = 'application/json') {
58
+ return new Request('https://app.example/api/assistant/session', {
59
+ method: 'POST', headers: { origin, 'content-type': contentType }, body: JSON.stringify(body),
60
+ });
61
+ }
62
+
63
+ describe('generated assistant route (synthetic contract)', () => {
64
+ it('returns JSON 401, not an HTML redirect, for a signed-out request', async () => {
65
+ const response = await POST(request());
66
+ expect(response.status).toBe(401);
67
+ expect(response.headers.get('location')).toBeNull();
68
+ expect(response.headers.get('cache-control')).toBe('no-store');
69
+ expect(response.headers.get('content-type')).toContain('application/json');
70
+ expect(upstream).not.toHaveBeenCalled();
71
+ });
72
+ it('rejects wrong origins and form requests before reading the application session', async () => {
73
+ expect((await POST(request({}, 'https://attacker.example'))).status).toBe(403);
74
+ expect((await POST(request({}, 'https://app.example', 'text/plain'))).status).toBe(415);
75
+ expect(authenticateAssistantRequest).not.toHaveBeenCalled();
76
+ expect(upstream).not.toHaveBeenCalled();
77
+ });
78
+ it('does not let browser JSON replace the backend identity or tenant routes', async () => {
79
+ vi.mocked(authenticateAssistantRequest).mockResolvedValue({ user: { id: 'person-a', tenant: 'tenant-a' } });
80
+ expect((await POST(request({ user: { id: 'person-b', tenant: 'tenant-b' } }))).status).toBe(400);
81
+ expect((await POST(request({ routing: { endpoints: { api: 'https://attacker.example' } } }))).status).toBe(400);
82
+ expect(upstream).not.toHaveBeenCalled();
83
+ });
84
+ it('exchanges only the verified identity and returns the short-lived session', async () => {
85
+ vi.mocked(authenticateAssistantRequest).mockResolvedValue({ user: { id: 'person-a', tenant: 'tenant-a' } });
86
+ const response = await POST(request({ context: { page: 'tasks' } }));
87
+ expect(response.status).toBe(200);
88
+ expect(await response.json()).toEqual(session);
89
+ expect(JSON.parse(String(upstream.mock.calls[0]?.[1]?.body))).toMatchObject({
90
+ user: { id: 'person-a', tenant: 'tenant-a' }, origin: 'https://app.example', context: { page: 'tasks' },
91
+ });
92
+ });
93
+ it('fails closed on a cross-tenant sign-in refusal without retrying the ticket', async () => {
94
+ vi.mocked(authenticateAssistantRequest).mockResolvedValue({ user: { id: 'person-a' }, signInTicket: 'synthetic-bound-ticket' });
95
+ upstream.mockResolvedValue(Response.json({ code: 'elevation_tenant_mismatch' }, { status: 403 }));
96
+ const response = await POST(request());
97
+ expect(response.status).toBe(403);
98
+ expect((await response.json()).code).toBe('elevation_tenant_mismatch');
99
+ expect(upstream).toHaveBeenCalledTimes(1);
100
+ });
101
+ });
102
+ // Add acceptance tests using your application's own signed-out, tenant-A and tenant-B session fixtures.
103
+ // These synthetic tests prove route wiring, not the membership rules inside authenticateAssistantRequest.
104
+ `;
105
+ }
106
+ //# sourceMappingURL=assistant-embed-tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-embed-tests.js","sourceRoot":"","sources":["../src/assistant-embed-tests.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;CAyBR,CAAC;AACF,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,uBAAuB;IACrC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwER,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Vue stays static; complex SDK properties are set before connecting the element, including CSRF. */
2
+ export declare function vueEmbedFiles(): Record<string, string>;
3
+ //# sourceMappingURL=assistant-embed-vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-embed-vue.d.ts","sourceRoot":"","sources":["../src/assistant-embed-vue.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0EtD"}
@@ -0,0 +1,77 @@
1
+ /** Vue stays static; complex SDK properties are set before connecting the element, including CSRF. */
2
+ export function vueEmbedFiles() {
3
+ return {
4
+ 'src/components/NoodleAssistant.vue': `<script setup lang="ts">
5
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
6
+ import type { NoodleAssistantElement } from '@noodleseed/assistant';
7
+ import { assistantSessionFetch } from '../lib/noodle-assistant-transport';
8
+
9
+ const props = defineProps<{ principalKey: string; csrfToken: string }>();
10
+ const host = ref<HTMLDivElement>();
11
+ let element: NoodleAssistantElement | undefined;
12
+ let disposed = false;
13
+
14
+ onMounted(async () => {
15
+ await import('@noodleseed/assistant');
16
+ if (disposed || !host.value) return;
17
+ element = document.createElement('noodle-assistant') as NoodleAssistantElement;
18
+ element.fetch = assistantSessionFetch(window.location.origin, () => props.csrfToken);
19
+ element.sessionEndpoint = '/api/assistant/session';
20
+ element.setAttribute('theme', 'auto');
21
+ host.value.append(element);
22
+ });
23
+ // This key clears browser state, never grants authority. Django re-verifies the actual session.
24
+ watch(() => props.principalKey, () => element?.resetSession());
25
+ onBeforeUnmount(() => { disposed = true; element?.remove(); element = undefined; });
26
+ </script>
27
+
28
+ <template><div ref="host" /></template>
29
+ `,
30
+ 'src/lib/noodle-assistant-transport.ts': `/** Preserve Django CSRF without forwarding the token to Noodle or another origin. */
31
+ export function assistantSessionFetch(
32
+ origin: string,
33
+ csrfToken: () => string,
34
+ transport: typeof fetch = fetch,
35
+ ): typeof fetch {
36
+ return (input, init) => {
37
+ const request = new Request(input instanceof Request ? input : new URL(input, origin), init);
38
+ const url = new URL(request.url);
39
+ if (url.origin === origin && url.pathname === '/api/assistant/session' && request.method === 'POST') {
40
+ const token = csrfToken();
41
+ if (!token) return Promise.reject(new Error('Application CSRF token is unavailable'));
42
+ request.headers.set('X-CSRFToken', token);
43
+ } else {
44
+ request.headers.delete('X-CSRFToken');
45
+ }
46
+ return transport(request);
47
+ };
48
+ }
49
+ `,
50
+ 'test/noodle-assistant-transport.test.ts': `import { describe, expect, it, vi } from 'vitest';
51
+ import { assistantSessionFetch } from '../src/lib/noodle-assistant-transport';
52
+
53
+ describe('Django session CSRF transport', () => {
54
+ it('adds the current X-CSRFToken only to the same-origin session POST', async () => {
55
+ const transport = vi.fn<typeof fetch>().mockResolvedValue(Response.json({}));
56
+ let token = 'synthetic-csrf-one';
57
+ const request = assistantSessionFetch('https://app.example', () => token, transport);
58
+ await request('/api/assistant/session', { method: 'POST', body: '{}' });
59
+ expect((transport.mock.calls[0]?.[0] as Request).headers.get('X-CSRFToken')).toBe(token);
60
+ token = 'synthetic-csrf-two';
61
+ await request('/api/assistant/session', { method: 'POST', body: '{}' });
62
+ expect((transport.mock.calls[1]?.[0] as Request).headers.get('X-CSRFToken')).toBe(token);
63
+ await request('https://cloud.example/v1/assistant/turns', { method: 'POST', headers: { 'X-CSRFToken': token } });
64
+ expect((transport.mock.calls[2]?.[0] as Request).headers.has('X-CSRFToken')).toBe(false);
65
+ });
66
+ it('fails closed before network activity without an application CSRF token', async () => {
67
+ const transport = vi.fn<typeof fetch>();
68
+ await expect(assistantSessionFetch('https://app.example', () => '', transport)(
69
+ '/api/assistant/session', { method: 'POST', body: '{}' },
70
+ )).rejects.toThrow('CSRF token');
71
+ expect(transport).not.toHaveBeenCalled();
72
+ });
73
+ });
74
+ `,
75
+ };
76
+ }
77
+ //# sourceMappingURL=assistant-embed-vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-embed-vue.js","sourceRoot":"","sources":["../src/assistant-embed-vue.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;CAyBzC;QACG,uCAAuC,EAAE;;;;;;;;;;;;;;;;;;;CAmB5C;QACG,yCAAyC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwB9C;KACE,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-embed-ops.d.ts","sourceRoot":"","sources":["../../src/commands/assistant-embed-ops.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAgHnD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,EAAE,cAAc,EACrB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,MAAM,CAAC,CA6EjB"}
1
+ {"version":3,"file":"assistant-embed-ops.d.ts","sourceRoot":"","sources":["../../src/commands/assistant-embed-ops.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAoInD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,EAAE,cAAc,EACrB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,MAAM,CAAC,CA8HjB"}