@noodleseed/one 0.133.0 → 0.134.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 (87) hide show
  1. package/dist/bin.js +0 -0
  2. package/dist/commands/author-loop.d.ts.map +1 -1
  3. package/dist/commands/author-loop.js +3 -3
  4. package/dist/commands/author-loop.js.map +1 -1
  5. package/dist/commands/devtools.js +1 -1
  6. package/dist/commands/devtools.js.map +1 -1
  7. package/dist/commands/local-readiness.d.ts +2 -2
  8. package/dist/commands/local-readiness.d.ts.map +1 -1
  9. package/dist/commands/local-readiness.js +15 -7
  10. package/dist/commands/local-readiness.js.map +1 -1
  11. package/dist/dev-local-runtime.d.ts +33 -0
  12. package/dist/dev-local-runtime.d.ts.map +1 -0
  13. package/dist/dev-local-runtime.js +53 -0
  14. package/dist/dev-local-runtime.js.map +1 -0
  15. package/dist/dev.d.ts +4 -0
  16. package/dist/dev.d.ts.map +1 -1
  17. package/dist/dev.js +109 -31
  18. package/dist/dev.js.map +1 -1
  19. package/dist/devtools-harness.js +5 -3
  20. package/dist/devtools-harness.js.map +1 -1
  21. package/dist/devtools-preview.d.ts +2 -0
  22. package/dist/devtools-preview.d.ts.map +1 -1
  23. package/dist/devtools-preview.js +15 -3
  24. package/dist/devtools-preview.js.map +1 -1
  25. package/dist/devtools-rpc-forwarder.js +1 -1
  26. package/dist/devtools-rpc-forwarder.js.map +1 -1
  27. package/dist/preview-session.d.ts.map +1 -1
  28. package/dist/preview-session.js +1 -0
  29. package/dist/preview-session.js.map +1 -1
  30. package/dist/react-widget-build.d.ts +4 -0
  31. package/dist/react-widget-build.d.ts.map +1 -1
  32. package/dist/react-widget-build.js +9 -0
  33. package/dist/react-widget-build.js.map +1 -1
  34. package/node_modules/@noodle-borg/admission-limits/dist/client-address.d.ts +7 -0
  35. package/node_modules/@noodle-borg/admission-limits/dist/counter-store.d.ts +71 -0
  36. package/node_modules/@noodle-borg/admission-limits/dist/envelope.d.ts +67 -0
  37. package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.d.ts +15 -0
  38. package/node_modules/@noodle-borg/admission-limits/dist/portable.d.ts +6 -0
  39. package/node_modules/@noodle-borg/admission-limits/dist/portable.js +6 -0
  40. package/node_modules/@noodle-borg/admission-limits/package.json +8 -0
  41. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  42. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +4 -1
  43. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/artifact-projection.d.ts +21 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-context.d.ts +25 -0
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-customer-issuer.d.ts +3 -0
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-customer-routing.d.ts +19 -0
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +39 -0
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interaction-state.d.ts +181 -0
  50. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.d.ts +99 -0
  51. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.js +1 -1
  52. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-presentation.d.ts +35 -0
  53. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-sensitive-values.d.ts +3 -0
  54. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +226 -0
  55. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +1 -1
  56. package/node_modules/@noodle-borg/assistant-gateway/dist/elevation-store.d.ts +70 -0
  57. package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +68 -0
  58. package/node_modules/@noodle-borg/assistant-gateway/dist/embed-script.d.ts +24 -0
  59. package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +73 -0
  60. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-elevation-store.d.ts +21 -0
  61. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.d.ts +28 -0
  62. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +24 -0
  63. package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +56 -0
  64. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +21 -0
  65. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +2 -1
  66. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +44 -0
  67. package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.d.ts +22 -0
  68. package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +16 -0
  69. package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +1 -1
  70. package/node_modules/@noodle-borg/assistant-gateway/dist/tenant-ref.d.ts +13 -0
  71. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +7 -0
  72. package/node_modules/@noodle-borg/authoring/dist/assistant.js +3 -0
  73. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +4 -0
  74. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +6 -5
  75. package/node_modules/@noodle-borg/protocol/dist/mapping.js +8 -0
  76. package/node_modules/@noodle-borg/service/dist/local-options.d.ts +9 -0
  77. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +14 -3
  78. package/node_modules/@noodle-borg/service/dist/routes/assistant-embeds.js +1 -1
  79. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +1 -1
  80. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +1 -1
  81. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +1 -1
  82. package/node_modules/@noodle-borg/service/dist/serve-local.js +2 -3
  83. package/node_modules/@noodle-borg/service/package.json +1 -1
  84. package/node_modules/@noodleseed/assistant/README.md +4 -1
  85. package/package.json +5 -3
  86. package/node_modules/@noodle-borg/admission-limits/dist/index.js +0 -15
  87. package/node_modules/@noodle-borg/admission-limits/dist/postgres-counter-store.js +0 -68
@@ -0,0 +1,70 @@
1
+ import type { TenantRef } from './tenant-ref.js';
2
+ /**
3
+ * Mid-conversation sign-in on a `mixed` surface (ADR 0201, 5.6b).
4
+ *
5
+ * **Not an interaction record, deliberately.** The plan proposed a new `auth_requested` kind on the
6
+ * existing interaction machinery, but that machinery is *visitor-resolvable*: accept/decline/cancel are
7
+ * driven from the widget. An elevation resolved by a visitor click would be an elevation with no
8
+ * credentials, so reusing it would have meant bolting a "this kind is not visitor-resolvable" exception
9
+ * onto every resolution path and hoping none was missed. A separate record has no such path to forget.
10
+ *
11
+ * **The continuation is a capability that travels through the browser**, so possession alone must be
12
+ * worthless: spending one also requires the customer's client credentials, and the claim checks that the
13
+ * client's tenant owns the session. It is stored hashed for the same reason session tokens are — the
14
+ * store never holds a value that could be replayed if it leaked.
15
+ */
16
+ export interface AssistantElevationRecord {
17
+ readonly id: string;
18
+ readonly sessionId: string;
19
+ readonly tenant: TenantRef;
20
+ /** The capability the visitor was refused, carried so the audit says what they signed in for. */
21
+ readonly tool: string;
22
+ readonly continuationHash: string;
23
+ readonly createdAt: string;
24
+ readonly expiresAt: string;
25
+ readonly claimedAt?: string;
26
+ }
27
+ export type AssistantElevationClaim = {
28
+ readonly ok: true;
29
+ readonly elevation: AssistantElevationRecord;
30
+ } | {
31
+ readonly ok: false;
32
+ /**
33
+ * One code per refusal, because they are operationally different: `unknown` is a bad or already
34
+ * spent continuation, `expired` is a visitor who took too long, and `tenant_mismatch` is a client
35
+ * spending a continuation for a conversation it does not own — the last is the one worth alerting
36
+ * on, so it must not collapse into the first.
37
+ */
38
+ readonly reason: 'unknown' | 'expired' | 'tenant_mismatch';
39
+ };
40
+ /** Ten minutes: long enough for a real login round trip, short enough that a leaked value is stale. */
41
+ export declare const ASSISTANT_ELEVATION_TTL_MS: number;
42
+ export interface AssistantElevationStore {
43
+ /**
44
+ * Open one elevation for a session. At most one may be live at a time — a second request supersedes
45
+ * the first rather than accumulating, so a model that asks twice cannot mint unbounded capabilities.
46
+ */
47
+ request(input: {
48
+ readonly sessionId: string;
49
+ readonly tenant: TenantRef;
50
+ readonly tool: string;
51
+ readonly now: Date;
52
+ }): Promise<{
53
+ readonly elevation: AssistantElevationRecord;
54
+ readonly continuation: string;
55
+ }>;
56
+ /**
57
+ * Spend a continuation exactly once. Atomic: two backends racing the same value must not both win,
58
+ * which is why this is one store seam rather than a read-then-write in the route.
59
+ */
60
+ claim(input: {
61
+ readonly continuation: string;
62
+ readonly tenant: TenantRef;
63
+ readonly now: Date;
64
+ }): Promise<AssistantElevationClaim>;
65
+ }
66
+ export declare function elevationContinuation(): string;
67
+ export declare function elevationDigest(value: string): string;
68
+ export declare function elevationHashesEqual(left: string, right: string): boolean;
69
+ export declare function sameTenant(left: TenantRef, right: TenantRef): boolean;
70
+ //# sourceMappingURL=elevation-store.d.ts.map
@@ -0,0 +1,68 @@
1
+ import type { AssistantPendingResume, AssistantSessionRecord } from './assistant-store.js';
2
+ import type { AssistantElevationStore } from './elevation-store.js';
3
+ import type { TenantRef } from './tenant-ref.js';
4
+ /**
5
+ * The elevation decision, with no HTTP in it.
6
+ *
7
+ * Kept transport-free for the same reason `mintPublicSession` is: the interesting part is an ordering of
8
+ * refusals, and an ordering is only testable without standing up a server. `packages/service` adapts.
9
+ *
10
+ * **The order is the contract.** The continuation is claimed *before* the session is touched, and the
11
+ * claim is tenant-checked, so a client reaching for a conversation it does not own never reaches the
12
+ * mutation at all. Claiming first also means a refused elevation cannot leave a spent continuation
13
+ * behind — and a spent one cannot be replayed, which is the same statement read from the other side.
14
+ */
15
+ export interface ElevationPorts {
16
+ readonly elevations: Pick<AssistantElevationStore, 'claim'>;
17
+ readonly elevateSession: (input: {
18
+ readonly sessionId: string;
19
+ readonly caller: AssistantSessionRecord['caller'];
20
+ readonly clientId: string;
21
+ readonly origin: string;
22
+ readonly customerRouting?: AssistantSessionRecord['customerRouting'];
23
+ readonly pendingResume?: AssistantPendingResume;
24
+ readonly now: Date;
25
+ }) => Promise<{
26
+ readonly ok: true;
27
+ readonly session: AssistantSessionRecord;
28
+ readonly token: string;
29
+ } | {
30
+ readonly ok: false;
31
+ readonly reason: 'unknown_session' | 'already_elevated';
32
+ }>;
33
+ readonly now: () => Date;
34
+ }
35
+ export interface ElevationRequest {
36
+ /** Presented by the customer's backend. Possession alone is not enough; see the route. */
37
+ readonly continuation: string;
38
+ /** The tenant of the *authenticated client*, never a value the caller supplied loose. */
39
+ readonly tenant: TenantRef;
40
+ readonly caller: AssistantSessionRecord['caller'];
41
+ /** The authenticated client's id: rebinds the session's issuer basis on success (ADR 0152). */
42
+ readonly clientId: string;
43
+ /**
44
+ * Where the conversation continues: allowlist-validated by the route BEFORE the ticket is
45
+ * claimed, then re-pinned onto the session so its CORS follows the visitor's post-login page.
46
+ */
47
+ readonly origin: string;
48
+ /** Backend-verified customer routes, validated by the route; absent means unchanged. */
49
+ readonly customerRouting?: AssistantSessionRecord['customerRouting'];
50
+ /**
51
+ * Arm the one-shot resume of the intercepted tool on success. Only an explicit `true` arms —
52
+ * the route owns the default policy, and probes (the doctor's synthetic elevation) pass nothing.
53
+ */
54
+ readonly resume?: boolean;
55
+ }
56
+ export type ElevationResult = {
57
+ readonly ok: true;
58
+ readonly session: AssistantSessionRecord;
59
+ readonly token: string;
60
+ /** What the visitor was refused before signing in, for the audit trail. */
61
+ readonly tool: string;
62
+ } | {
63
+ readonly ok: false;
64
+ readonly status: 403 | 409;
65
+ readonly code: 'elevation_ticket_invalid' | 'elevation_ticket_expired' | 'elevation_tenant_mismatch' | 'elevation_session_unavailable' | 'elevation_already_signed_in';
66
+ };
67
+ export declare function completeElevation(request: ElevationRequest, ports: ElevationPorts): Promise<ElevationResult>;
68
+ //# sourceMappingURL=elevation.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The browser embed script, read from the published `@noodleseed/assistant` build.
3
+ *
4
+ * Served from the service origin rather than a CDN (ADR 0201): there is no new release lane to run, the
5
+ * script is always the one that matches the deployed service, and a CDN can front the URL later without
6
+ * changing a single byte a customer pasted.
7
+ *
8
+ * Read once and cached. The file is immutable for the life of the process — it ships inside the image —
9
+ * so re-reading it per request would buy nothing but syscalls.
10
+ */
11
+ export interface EmbedScript {
12
+ readonly bytes: Buffer;
13
+ /** Content-derived, so a redeployed service with an unchanged bundle keeps browsers' caches warm. */
14
+ readonly etag: string;
15
+ }
16
+ export type EmbedScriptResult = {
17
+ readonly ok: true;
18
+ readonly script: EmbedScript;
19
+ } | {
20
+ readonly ok: false;
21
+ readonly reason: string;
22
+ };
23
+ export declare function loadEmbedScript(): EmbedScriptResult;
24
+ //# sourceMappingURL=embed-script.d.ts.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Public embed identifiers.
3
+ *
4
+ * These live in their own table rather than as a flag on the authenticated `assistant_clients` row, and
5
+ * the reason is structural rather than tidiness: a public embed id is printed in page source, so it must
6
+ * never be a credential. A record with no secret column cannot accidentally acquire one — there is
7
+ * nothing for a future code path to populate, hash, or compare. Reusing the client table would have
8
+ * inherited its tooling for free while keeping a `secret_hash` one bug away from a public identifier.
9
+ *
10
+ * The id is stable across deploys. A customer pastes the snippet once; redeploys and rollbacks swap the
11
+ * projection underneath a page that never changes. Origins, capabilities, and UI therefore come from the
12
+ * *active* deployment at mint time, never from the embed record.
13
+ */
14
+ export interface PublicEmbedRecord {
15
+ /** Printed in page source. Non-secret by construction. */
16
+ readonly embedId: string;
17
+ readonly org: string;
18
+ readonly app: string;
19
+ readonly env: string;
20
+ /**
21
+ * Which surface of the assistant this id addresses, so budgets and kill switches key per surface.
22
+ * Provisioning-time record only: `ensure` never updates a live row, so this goes stale when a
23
+ * redeploy changes the mode — readers report the active deployment's mode instead.
24
+ */
25
+ readonly surfaceMode: 'public' | 'mixed';
26
+ readonly createdAt: Date;
27
+ readonly revokedAt?: Date;
28
+ /**
29
+ * Operator overrides for this surface's daily caps. Undefined means "use the deployment defaults";
30
+ * `0` is a real value and is the kill switch, so the two must stay distinguishable everywhere.
31
+ */
32
+ readonly turnsPerDay?: number;
33
+ readonly mintsPerDay?: number;
34
+ }
35
+ /** What an operator may change about a surface. An omitted field leaves that cap as it was. */
36
+ export interface PublicEmbedBudget {
37
+ readonly turnsPerDay?: number;
38
+ readonly mintsPerDay?: number;
39
+ }
40
+ export interface PublicEmbedStore {
41
+ /**
42
+ * Return the existing id for this tenant surface, or create one. Deploy calls this, so it must be
43
+ * idempotent: a redeploy that minted a fresh id would silently break every page already carrying the
44
+ * old one.
45
+ */
46
+ ensure(input: {
47
+ readonly org: string;
48
+ readonly app: string;
49
+ readonly env: string;
50
+ readonly surfaceMode: 'public' | 'mixed';
51
+ readonly now: Date;
52
+ }): Promise<PublicEmbedRecord>;
53
+ /** Resolve an id presented by a browser. Revoked ids resolve to undefined. */
54
+ lookup(embedId: string): Promise<PublicEmbedRecord | undefined>;
55
+ /**
56
+ * Raise, lower, or switch off a surface's daily caps. Separate from `ensure` on purpose: `ensure` runs
57
+ * on every deploy and never overwrites a live row, so a redeploy cannot reset an operator's decision.
58
+ */
59
+ setBudget(embedId: string, budget: PublicEmbedBudget, now: Date): Promise<PublicEmbedRecord | undefined>;
60
+ list(tenant: {
61
+ readonly org: string;
62
+ readonly app: string;
63
+ readonly env: string;
64
+ }): Promise<readonly PublicEmbedRecord[]>;
65
+ revoke(embedId: string, now: Date): Promise<boolean>;
66
+ }
67
+ /**
68
+ * `pub_` prefixed so it is self-describing in page source, logs, and support threads — someone reading a
69
+ * customer's HTML can tell at a glance that it is not a secret that leaked.
70
+ */
71
+ export declare const PUBLIC_EMBED_ID_PREFIX = "pub_";
72
+ export declare function isPublicEmbedId(value: string): boolean;
73
+ //# sourceMappingURL=embed-store.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { type AssistantElevationClaim, type AssistantElevationRecord, type AssistantElevationStore } from './elevation-store.js';
2
+ import type { TenantRef } from './tenant-ref.js';
3
+ /** The in-memory half of the pair. Both run the shared parity suite; neither is the only proof. */
4
+ export declare class InMemoryAssistantElevationStore implements AssistantElevationStore {
5
+ #private;
6
+ request(input: {
7
+ readonly sessionId: string;
8
+ readonly tenant: TenantRef;
9
+ readonly tool: string;
10
+ readonly now: Date;
11
+ }): Promise<{
12
+ readonly elevation: AssistantElevationRecord;
13
+ readonly continuation: string;
14
+ }>;
15
+ claim(input: {
16
+ readonly continuation: string;
17
+ readonly tenant: TenantRef;
18
+ readonly now: Date;
19
+ }): Promise<AssistantElevationClaim>;
20
+ }
21
+ //# sourceMappingURL=in-memory-elevation-store.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { type PublicEmbedBudget, type PublicEmbedRecord, type PublicEmbedStore } from './embed-store.js';
2
+ /** 120 bits of randomness, base36: unguessable enough that an id is never a discovery oracle. */
3
+ export declare function newPublicEmbedId(): string;
4
+ export declare function tenantKey(tenant: {
5
+ readonly org: string;
6
+ readonly app: string;
7
+ readonly env: string;
8
+ }): string;
9
+ /** Process-local embeds for local development and tests. */
10
+ export declare class InMemoryPublicEmbedStore implements PublicEmbedStore {
11
+ #private;
12
+ ensure(input: {
13
+ readonly org: string;
14
+ readonly app: string;
15
+ readonly env: string;
16
+ readonly surfaceMode: 'public' | 'mixed';
17
+ readonly now: Date;
18
+ }): Promise<PublicEmbedRecord>;
19
+ setBudget(embedId: string, budget: PublicEmbedBudget, _now: Date): Promise<PublicEmbedRecord | undefined>;
20
+ lookup(embedId: string): Promise<PublicEmbedRecord | undefined>;
21
+ list(tenant: {
22
+ readonly org: string;
23
+ readonly app: string;
24
+ readonly env: string;
25
+ }): Promise<readonly PublicEmbedRecord[]>;
26
+ revoke(embedId: string, now: Date): Promise<boolean>;
27
+ }
28
+ //# sourceMappingURL=in-memory-embed-store.d.ts.map
@@ -0,0 +1,24 @@
1
+ /** Assistant gateway contracts and in-memory adapters safe for the published CLI runtime. */
2
+ export * from './artifact-projection.js';
3
+ export * from './assistant-context.js';
4
+ export * from './assistant-customer-issuer.js';
5
+ export * from './assistant-customer-routing.js';
6
+ export * from './assistant-guide.js';
7
+ export * from './assistant-interaction-state.js';
8
+ export * from './assistant-interactive.js';
9
+ export * from './assistant-presentation.js';
10
+ export * from './assistant-sensitive-values.js';
11
+ export * from './assistant-store.js';
12
+ export * from './elevation.js';
13
+ export * from './elevation-store.js';
14
+ export * from './embed-script.js';
15
+ export * from './embed-store.js';
16
+ export * from './in-memory-elevation-store.js';
17
+ export * from './in-memory-embed-store.js';
18
+ export * from './public-session.js';
19
+ export * from './public-surface.js';
20
+ export * from './public-turn.js';
21
+ export * from './session-resume.js';
22
+ export * from './surface-budget.js';
23
+ export * from './tenant-ref.js';
24
+ //# sourceMappingURL=portable.d.ts.map
@@ -0,0 +1,56 @@
1
+ import type { AdmissionEnvelope, DailyCounterStore } from '@noodle-borg/admission-limits/portable';
2
+ import { type PublicEmbedRecord, type PublicEmbedStore } from './embed-store.js';
3
+ import type { PublicSurface } from './public-surface.js';
4
+ /**
5
+ * Minting an anonymous session for a public page.
6
+ *
7
+ * Transport-free on purpose: the hosting service owns the HTTP adapter (parse body, map result), and
8
+ * everything that decides *whether* a stranger gets a session lives here, behind narrow ports. That is
9
+ * what keeps the decision testable without a server and the service-side residue to a few lines.
10
+ *
11
+ * The refusal order is part of the contract, not an implementation detail. ADR 0201 §8 requires
12
+ * admission to complete before any metered work, so the store-durability and budget checks come before
13
+ * anything that would read deployment state or reach a model.
14
+ */
15
+ export interface PublicSessionPorts {
16
+ readonly embeds: Pick<PublicEmbedStore, 'lookup'>;
17
+ readonly counters: DailyCounterStore;
18
+ /** The live surface for this tenant, read from the *active* deployment at mint time. */
19
+ resolveActiveSurface(embed: PublicEmbedRecord): Promise<PublicSurface | undefined>;
20
+ /** Persist the anonymous session and return its bearer token and expiry. */
21
+ createSession(input: {
22
+ readonly embed: PublicEmbedRecord;
23
+ readonly origin: string;
24
+ readonly subject: string;
25
+ readonly envelope: AdmissionEnvelope;
26
+ }): Promise<{
27
+ readonly token: string;
28
+ readonly expiresAt: string;
29
+ }>;
30
+ /** Opaque, deployment-scoped, unlinkable to any person. */
31
+ newAnonymousSubject(): string;
32
+ now(): Date;
33
+ }
34
+ export interface PublicSessionRequest {
35
+ readonly embedId: unknown;
36
+ readonly origin: unknown;
37
+ /**
38
+ * The visitor's address bucket (admission tier 3), already hashed by the transport — a raw address
39
+ * must never reach this decision, a counter key, or an audit payload. Absent when ingress could not
40
+ * be parsed, in which case the surface tier alone applies: fairness degrades, solvency does not.
41
+ */
42
+ readonly addressBucket?: string | undefined;
43
+ }
44
+ export type PublicSessionResult = {
45
+ readonly ok: true;
46
+ readonly token: string;
47
+ readonly expiresAt: string;
48
+ readonly embed: PublicEmbedRecord;
49
+ } | {
50
+ readonly ok: false;
51
+ readonly status: number;
52
+ readonly code: string;
53
+ readonly message: string;
54
+ };
55
+ export declare function mintPublicSession(request: PublicSessionRequest, envelope: AdmissionEnvelope, ports: PublicSessionPorts): Promise<PublicSessionResult>;
56
+ //# sourceMappingURL=public-session.d.ts.map
@@ -0,0 +1,21 @@
1
+ import type { SurfaceCapabilityRef } from './artifact-projection.js';
2
+ /**
3
+ * The one reader of "does this deployment face the public, and on what terms".
4
+ *
5
+ * Three callers ask that question and must never disagree: deploy decides whether to provision an embed
6
+ * id, minting checks the browser's origin against the surface, and the session's projection narrows the
7
+ * artifact to the surface's capabilities. When each answered locally, a surface shape one of them
8
+ * accepted and another rejected would silently mint sessions that could reach nothing, or provision an id
9
+ * for a surface that refuses to serve it.
10
+ *
11
+ * The argument is `unknown` because the assistant block arrives as compiled artifact data rather than a
12
+ * typed authoring value; the shape is validated here, once, rather than asserted at three call sites.
13
+ */
14
+ export interface PublicSurface {
15
+ readonly mode: 'public' | 'mixed';
16
+ readonly origins: readonly string[];
17
+ readonly capabilities: readonly SurfaceCapabilityRef[];
18
+ readonly instructions?: string;
19
+ }
20
+ export declare function publicSurfaceOf(assistant: unknown): PublicSurface | undefined;
21
+ //# sourceMappingURL=public-surface.d.ts.map
@@ -3,7 +3,7 @@ export function publicSurfaceOf(assistant) {
3
3
  if (!Array.isArray(surfaces))
4
4
  return undefined;
5
5
  for (const entry of surfaces) {
6
- const { mode, origins, capabilities } = entry;
6
+ const { mode, origins, capabilities, instructions } = entry;
7
7
  // A mixed surface admits strangers exactly as a public one does — sign-in only widens what a visitor
8
8
  // reaches after elevation, so both provision an embed id and both project.
9
9
  if (mode !== 'public' && mode !== 'mixed')
@@ -17,6 +17,7 @@ export function publicSurfaceOf(assistant) {
17
17
  capabilities: Array.isArray(capabilities)
18
18
  ? capabilities
19
19
  : [],
20
+ ...(typeof instructions === 'string' ? { instructions } : {}),
20
21
  };
21
22
  }
22
23
  return undefined;
@@ -0,0 +1,44 @@
1
+ import type { AdmissionEnvelope, DailyCounterStore } from '@noodle-borg/admission-limits/portable';
2
+ import type { AssistantTurnConsumption } from './assistant-store.js';
3
+ import type { PublicEmbedStore } from './embed-store.js';
4
+ /**
5
+ * Whether an anonymous visitor's next turn runs.
6
+ *
7
+ * Transport-free for the same reason {@link ./public-session.ts} is: the decision about who gets to spend
8
+ * a customer's model budget should be provable without standing up a server. The service adapts it.
9
+ *
10
+ * The envelope arrives per call rather than being snapshotted onto the session at mint time, and that is
11
+ * load-bearing: `noodle assistant budget set --turns-per-day 0` must stop conversations already under
12
+ * way, which is precisely the case an operator reaches for the kill switch to stop. Re-reading it each
13
+ * turn makes the switch fall out of the ordinary path instead of needing a second mechanism.
14
+ */
15
+ export interface PublicTurnPorts {
16
+ readonly counters: DailyCounterStore;
17
+ /** Re-read per turn: it carries the operator's live budget, and whether the surface still exists. */
18
+ readonly embeds: Pick<PublicEmbedStore, 'lookup'>;
19
+ /** Spend one of this session's turns, atomically (admission tier 1). */
20
+ consumeTurn(id: string, limit: number): Promise<AssistantTurnConsumption>;
21
+ now(): Date;
22
+ }
23
+ export interface PublicTurnRequest {
24
+ readonly sessionId: string;
25
+ /** The surface the session was admitted through; daily spend is charged against it. */
26
+ readonly publicEmbedId: string;
27
+ readonly message: string;
28
+ /**
29
+ * The visitor's hashed address bucket (admission tier 3), or absent when ingress could not be parsed.
30
+ * A raw address must never reach this decision, a counter key, or an audit payload.
31
+ */
32
+ readonly addressBucket?: string | undefined;
33
+ }
34
+ export type PublicTurnResult = {
35
+ readonly ok: true;
36
+ readonly turnCount: number;
37
+ } | {
38
+ readonly ok: false;
39
+ readonly status: number;
40
+ readonly code: string;
41
+ readonly message: string;
42
+ };
43
+ export declare function admitPublicTurn(request: PublicTurnRequest, defaults: AdmissionEnvelope, ports: PublicTurnPorts): Promise<PublicTurnResult>;
44
+ //# sourceMappingURL=public-turn.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The post-sign-in resume decision and its platform message (issue #1177). When a spent ticket
3
+ * elevates a session, the service may run one assistant turn so the model re-attempts the
4
+ * intercepted intent under the new principal — the visitor signed in to get an answer, and the
5
+ * panel's first content should be that answer, not silence. Transport-free like the elevation
6
+ * decision; `packages/service` adapts.
7
+ */
8
+ /**
9
+ * Whether a successful elevation arms the one-shot resume. ON by default — the demo-magic case is
10
+ * the default experience — with the exchange-side `resume: false` as the single disable knob (it
11
+ * lives in the integrator's own session endpoint, the code that spends the ticket).
12
+ */
13
+ export declare function shouldAutoResume(override: boolean | undefined): boolean;
14
+ /**
15
+ * The `[platform]` message that drives the resume turn, persisted to history as a user-role
16
+ * message like the interaction-resolution narration. It must countermand the interception's own
17
+ * guidance ("ask them to sign in, then offer to try again") — the sign-in already happened, and
18
+ * asking the visitor to repeat themselves is the silence this feature removes. Arguments are not
19
+ * replayed: the model re-derives them from history under the new principal.
20
+ */
21
+ export declare function resumeTurnMessage(tool: string): string;
22
+ //# sourceMappingURL=session-resume.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { type AdmissionEnvelope } from '@noodle-borg/admission-limits/portable';
2
+ import type { PublicEmbedRecord } from './embed-store.js';
3
+ /**
4
+ * One surface's daily budget, resolved from the operator's override and the deployment's defaults.
5
+ *
6
+ * Per surface rather than per service, because the question a customer actually asks is "does capping
7
+ * my marketing bot throttle my in-app assistant" — and the answer has to be an obvious no. The override
8
+ * lives on the embed row, which `ensure` never overwrites, so a redeploy cannot silently reset a kill
9
+ * switch someone reached for.
10
+ *
11
+ * Routed through `clamp` rather than used directly: the structural maximum is not negotiable by an
12
+ * operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
13
+ * ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
14
+ */
15
+ export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay'>): AdmissionEnvelope;
16
+ //# sourceMappingURL=surface-budget.d.ts.map
@@ -1,4 +1,4 @@
1
- import { clamp } from '@noodle-borg/admission-limits';
1
+ import { clamp } from '@noodle-borg/admission-limits/portable';
2
2
  /**
3
3
  * One surface's daily budget, resolved from the operator's override and the deployment's defaults.
4
4
  *
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The tenant coordinate an assistant session is bound to.
3
+ *
4
+ * Structurally re-declared rather than imported: `packages/service`'s `store.ts` remains the canonical
5
+ * definition, but the gateway must not depend on the hosted service (the dependency runs the other way).
6
+ * The two shapes are identical by contract — widen them together.
7
+ */
8
+ export interface TenantRef {
9
+ readonly org: string;
10
+ readonly app: string;
11
+ readonly env: string;
12
+ }
13
+ //# sourceMappingURL=tenant-ref.d.ts.map
@@ -143,6 +143,7 @@ export type AssistantSurfaceMode = 'authenticated' | 'public' | 'mixed';
143
143
  export interface AuthenticatedWebsiteAccess {
144
144
  readonly mode: 'authenticated';
145
145
  readonly origins: readonly string[];
146
+ readonly instructions?: string;
146
147
  /** Allowlist of verified session claims; undeclared claims are dropped at session exchange. */
147
148
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
148
149
  readonly capabilities?: readonly CapabilityRef[];
@@ -151,16 +152,21 @@ export interface PublicWebsiteAccess {
151
152
  readonly mode: 'public' | 'mixed';
152
153
  readonly origins: readonly string[];
153
154
  readonly capabilities: readonly CapabilityRef[];
155
+ readonly instructions?: string;
154
156
  }
155
157
  export type AssistantAccess = AuthenticatedWebsiteAccess | PublicWebsiteAccess;
156
158
  export interface AuthenticatedWebsiteInput {
157
159
  readonly origins: readonly (string | ConfigRef)[];
160
+ /** Assistant-only behavior for this exact signed-in website audience. */
161
+ readonly instructions?: string;
158
162
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
159
163
  /** Optional narrowing; omitted means the signed-in surface projects the whole server. */
160
164
  readonly capabilities?: readonly CapabilityRef[];
161
165
  }
162
166
  export interface PublicWebsiteInput {
163
167
  readonly origins: readonly (string | ConfigRef)[];
168
+ /** Assistant-only behavior for anonymous visitors on this website surface. */
169
+ readonly instructions?: string;
164
170
  /**
165
171
  * Required: on a public page this list *is* the externally reachable surface, so it must be an
166
172
  * explicit positive choice rather than whatever the server happens to declare.
@@ -186,6 +192,7 @@ export interface EmbeddedAssistantOptions extends AssistantUiOptions {
186
192
  export interface AssistantSurfaceConfig {
187
193
  readonly mode: AssistantSurfaceMode;
188
194
  readonly origins: readonly string[];
195
+ readonly instructions?: string;
189
196
  readonly capabilities?: readonly AssistantCapability[];
190
197
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
191
198
  }
@@ -3,6 +3,7 @@ export function authenticatedWebsite(input) {
3
3
  return {
4
4
  mode: 'authenticated',
5
5
  origins: serializeOrigins(input.origins, 'assistant.access.origins'),
6
+ ...(input.instructions === undefined ? {} : { instructions: input.instructions }),
6
7
  ...(input.sessionClaims ? { sessionClaims: structuredClone(input.sessionClaims) } : {}),
7
8
  ...(input.capabilities ? { capabilities: [...input.capabilities] } : {}),
8
9
  };
@@ -12,6 +13,7 @@ export function publicWebsite(input) {
12
13
  mode: input.signIn === true ? 'mixed' : 'public',
13
14
  origins: serializeOrigins(input.origins, 'assistant.access.origins'),
14
15
  capabilities: [...input.capabilities],
16
+ ...(input.instructions === undefined ? {} : { instructions: input.instructions }),
15
17
  };
16
18
  }
17
19
  function serializeOrigins(origins, path) {
@@ -42,6 +44,7 @@ export function embeddedAssistant(input) {
42
44
  surfaces: surfaces.map((surface) => ({
43
45
  mode: surface.mode,
44
46
  origins: [...surface.origins],
47
+ ...(surface.instructions === undefined ? {} : { instructions: surface.instructions }),
45
48
  ...(surface.capabilities
46
49
  ? { capabilities: normalizeCapabilities(surface.capabilities) }
47
50
  : {}),
@@ -339,6 +339,7 @@ export declare const manifestV1Schema: z.ZodObject<{
339
339
  mixed: "mixed";
340
340
  }>;
341
341
  origins: z.ZodArray<z.ZodString>;
342
+ instructions: z.ZodOptional<z.ZodString>;
342
343
  capabilities: z.ZodOptional<z.ZodArray<z.ZodObject<{
343
344
  kind: z.ZodEnum<{
344
345
  tool: "tool";
@@ -789,6 +790,7 @@ export declare const manifestV2Schema: z.ZodObject<{
789
790
  mixed: "mixed";
790
791
  }>;
791
792
  origins: z.ZodArray<z.ZodString>;
793
+ instructions: z.ZodOptional<z.ZodString>;
792
794
  capabilities: z.ZodOptional<z.ZodArray<z.ZodObject<{
793
795
  kind: z.ZodEnum<{
794
796
  tool: "tool";
@@ -1449,6 +1451,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1449
1451
  mixed: "mixed";
1450
1452
  }>;
1451
1453
  origins: z.ZodArray<z.ZodString>;
1454
+ instructions: z.ZodOptional<z.ZodString>;
1452
1455
  capabilities: z.ZodOptional<z.ZodArray<z.ZodObject<{
1453
1456
  kind: z.ZodEnum<{
1454
1457
  tool: "tool";
@@ -1898,6 +1901,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1898
1901
  mixed: "mixed";
1899
1902
  }>;
1900
1903
  origins: z.ZodArray<z.ZodString>;
1904
+ instructions: z.ZodOptional<z.ZodString>;
1901
1905
  capabilities: z.ZodOptional<z.ZodArray<z.ZodObject<{
1902
1906
  kind: z.ZodEnum<{
1903
1907
  tool: "tool";
@@ -334,6 +334,11 @@ const assistantCapabilitySchema = z
334
334
  name: z.string().trim().min(1),
335
335
  })
336
336
  .strict();
337
+ /**
338
+ * Upper bound on server- and surface-level instructions. These are concise behavior primers, not
339
+ * documentation; the shared cap keeps prompt ownership predictable across both layers.
340
+ */
341
+ const MAX_SERVER_INSTRUCTIONS = 4000;
337
342
  /** Verified session claims an embedding backend may pass at exchange; undeclared keys are dropped. */
338
343
  const sessionClaimsSchema = z.record(z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,63}$/, 'claim keys are identifier-style'), z.object({ exposeToModel: z.boolean().optional() }).strict());
339
344
  const embeddedAssistantSchema = z
@@ -356,6 +361,7 @@ const embeddedAssistantSchema = z
356
361
  .object({
357
362
  mode: z.enum(['authenticated', 'public', 'mixed']),
358
363
  origins: z.array(assistantOriginOrVariableSchema).min(1),
364
+ instructions: z.string().trim().min(1).max(MAX_SERVER_INSTRUCTIONS).optional(),
359
365
  capabilities: z.array(assistantCapabilitySchema).optional(),
360
366
  sessionClaims: sessionClaimsSchema.optional(),
361
367
  })
@@ -427,11 +433,6 @@ const serverShellSchema = z
427
433
  persistentActions: z.array(serverShellActionSchema).optional(),
428
434
  })
429
435
  .strict();
430
- /**
431
- * Upper bound on `server.instructions`. Hosts inject instructions into every session's context, so the
432
- * field is for a concise usage primer (a few hundred words), not documentation.
433
- */
434
- const MAX_SERVER_INSTRUCTIONS = 4000;
435
436
  const serverContextSchema = z
436
437
  .object({
437
438
  defaults: z