@boboddy/sdk 0.5.1 → 0.5.3

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.
@@ -18,3 +18,32 @@ export declare function tryComputeTopoRanks(nodeDefinitions: readonly Pick<NodeD
18
18
  * `tryComputeTopoRanks` does.
19
19
  */
20
20
  export declare function tryOrderNodeDefinitionsByTopoRank(nodeDefinitions: readonly NodeDefinitionSpec[], dependencyEdges: readonly DependencyEdgeSpec[]): NodeDefinitionSpec[] | null;
21
+ /**
22
+ * For every node reachable from `entryNodeKey`, the set of nodes that
23
+ * *dominate* it — every node that appears on *every* path from the entry to
24
+ * it, including the node itself (`dom(n)` always contains `n`). Standard
25
+ * iterative dataflow (Cooper/Harvey/Kennedy-style):
26
+ * `dom(entry) = {entry}`; `dom(n) = {n} ∪ ⋂ dom(p)` for each predecessor `p`
27
+ * of `n`, iterated to a fixed point.
28
+ *
29
+ * Unlike `tryComputeTopoRanks` above — which deliberately treats *every*
30
+ * in-degree-zero node as its own rank-0 root, so a graph with several
31
+ * disconnected entry points still ranks (see lines 51-56) — a dominator
32
+ * relation is only meaningful relative to a single root: "runs on every
33
+ * path from the entry" has no answer if there could be more than one
34
+ * entry. Callers must supply that root explicitly as `entryNodeKey`
35
+ * (`PipelineDefinitionSpec.entryNodeKey`, the node `config.startAt` names)
36
+ * rather than have it inferred from in-degree.
37
+ *
38
+ * Returns `null` (does not throw), same as `tryComputeTopoRanks`, when
39
+ * `entryNodeKey` does not name a node in `nodeDefinitions`, when
40
+ * `dependencyEdges` references a node key outside `nodeDefinitions`, or
41
+ * when the graph contains a cycle — every caller here is a diagnostic
42
+ * tool, not a build-time assertion (see `tryComputeTopoRanks`'s own doc
43
+ * comment), so it degrades gracefully rather than crashing validation.
44
+ *
45
+ * Nodes unreachable from `entryNodeKey` are simply absent from the
46
+ * returned map — an unreachable node can never appear on a path from the
47
+ * entry, so it neither dominates nor is dominated by anything reachable.
48
+ */
49
+ export declare function tryComputeDominators(nodeDefinitions: readonly Pick<NodeDefinitionSpec, "nodeKey">[], dependencyEdges: readonly DependencyEdgeSpec[], entryNodeKey: string): Map<string, ReadonlySet<string>> | null;
@@ -17,11 +17,3 @@ export declare function compileFanOutState(stateKey: string, state: FanOutState,
17
17
  export declare function compileParallelState(stateKey: string, state: ParallelState, ctx: CompileContext): CompiledState;
18
18
  export declare function compileLoopState(stateKey: string, state: LoopState, ctx: CompileContext): CompiledState;
19
19
  export declare function compileTerminalState(stateKey: string, kind: "succeed" | "fail"): CompiledState;
20
- /**
21
- * The SDK-side mirror of §6's domain invariant: a node may have more than
22
- * one incoming edge only when every source is a `choice`/`loop` state
23
- * (never an unconditional `step`/`fanOut`/`parallel`/`cohortGate`
24
- * successor) — gives authors a fast local error instead of a round-trip to
25
- * the server.
26
- */
27
- export declare function assertNoIllegalConvergentEdges(pipelineKey: string, nodeKindByKey: ReadonlyMap<string, NodeDefinitionSpec["kind"]>, edges: readonly DependencyEdgeSpec[]): void;
@@ -117,6 +117,8 @@ export type PipelineDefinitionSpec = {
117
117
  version: number;
118
118
  status: "draft" | "active" | "archived";
119
119
  inputSchemaJson?: Record<string, unknown> | null;
120
+ /** The node the pipeline begins execution at — mirrors `config.startAt`. */
121
+ entryNodeKey: string;
120
122
  nodeDefinitions: NodeDefinitionSpec[];
121
123
  dependencyEdges: DependencyEdgeSpec[];
122
124
  /** Step specs referenced by this pipeline. Used by the push command to auto-push steps that aren't explicitly exported. */
@@ -14984,25 +14984,6 @@ function compileLoopState(stateKey, state, ctx) {
14984
14984
  function compileTerminalState(stateKey, kind) {
14985
14985
  return { nodeDefinitions: [{ nodeKey: stateKey, kind }], edges: [] };
14986
14986
  }
14987
- function assertNoIllegalConvergentEdges(pipelineKey, nodeKindByKey, edges) {
14988
- const incoming = new Map;
14989
- for (const edge of edges) {
14990
- const list = incoming.get(edge.toNodeKey) ?? [];
14991
- list.push(edge);
14992
- incoming.set(edge.toNodeKey, list);
14993
- }
14994
- for (const [targetKey, incomingEdges] of incoming) {
14995
- if (incomingEdges.length <= 1)
14996
- continue;
14997
- const hasInvalidSource = incomingEdges.some((edge) => {
14998
- const kind = nodeKindByKey.get(edge.fromNodeKey);
14999
- return kind !== "choice" && kind !== "loop";
15000
- });
15001
- if (hasInvalidSource) {
15002
- throw new Error(`Pipeline "${pipelineKey}": state "${targetKey}" has more than one incoming edge, but not every source is a 'choice'/'loop' state (unconditional convergent edges are not allowed \u2014 see docs/research/flat-pipeline-sdk-and-visual-designer.md \xA76).`);
15003
- }
15004
- }
15005
- }
15006
14987
 
15007
14988
  // src/definitions/pipelines/define-pipeline.ts
15008
14989
  function isWorkingNodeDefinition(node) {
@@ -15039,8 +15020,6 @@ function definePipeline(config2) {
15039
15020
  nodeDefinitions.push(...compiled.nodeDefinitions);
15040
15021
  dependencyEdges.push(...compiled.edges);
15041
15022
  }
15042
- const nodeKindByKey = new Map(nodeDefinitions.map((node) => [node.nodeKey, node.kind]));
15043
- assertNoIllegalConvergentEdges(config2.key, nodeKindByKey, dependencyEdges);
15044
15023
  let inputSchemaJson = null;
15045
15024
  if (config2.input) {
15046
15025
  try {
@@ -15056,6 +15035,7 @@ function definePipeline(config2) {
15056
15035
  version: config2.version ?? 1,
15057
15036
  status: config2.status ?? "active",
15058
15037
  inputSchemaJson,
15038
+ entryNodeKey: config2.startAt,
15059
15039
  _stepDefinitions: [...stepDefMap.values()],
15060
15040
  nodeDefinitions,
15061
15041
  dependencyEdges
@@ -16748,6 +16728,7 @@ var buildPipelineDefinitionsClient = (pipelineDefinitions) => {
16748
16728
  description: spec.description,
16749
16729
  status: spec.status,
16750
16730
  inputSchemaJson: spec.inputSchemaJson,
16731
+ entryNodeKey: spec.entryNodeKey,
16751
16732
  nodeDefinitions,
16752
16733
  dependencyEdges
16753
16734
  };
@@ -1,5 +1,6 @@
1
1
  import type { ZodType } from "zod";
2
- import type { SignalKeysOf, SignalTypeMapOf, TypedStepDefinitionSpec } from "./define-step";
2
+ import type { EffectiveResult, SignalKeysOf, SignalTypeMapOf, TypedStepDefinitionSpec } from "./define-step";
3
+ import type { AnyStepFeature, FeatureSignalKeys } from "./step-features";
3
4
  /**
4
5
  * `codeStep()`'s own signal spec shape — unlike `defineStep()`'s
5
6
  * `SignalSpecInput`, `type` is required rather than inferred from the
@@ -17,7 +18,7 @@ export type CodeStepSignalSpec = {
17
18
  availableWhenResultStatusIn?: string[] | null;
18
19
  };
19
20
  export type CodeStepFn<TInput, TResult> = (input: TInput) => TResult | Promise<TResult>;
20
- export type DefineCodeStepInput<TInput extends ZodType = ZodType, TResult extends ZodType = ZodType> = {
21
+ export type DefineCodeStepInput<TInput extends ZodType = ZodType, TResult extends ZodType = ZodType, TFeatures extends ReadonlyArray<AnyStepFeature> = never[]> = {
21
22
  key: string;
22
23
  name: string;
23
24
  description?: string | null;
@@ -30,11 +31,22 @@ export type DefineCodeStepInput<TInput extends ZodType = ZodType, TResult extend
30
31
  * Must be a plain named export of the same module `codeStep()` is
31
32
  * called from (see docs/research/flat-pipeline-sdk-and-visual-designer.md
32
33
  * §7.7/§8's "code-step entrypoints resolve against the target repo").
34
+ *
35
+ * Typed against `EffectiveResult`, not the bare `resultSchema` output, so
36
+ * a step with `features: [Features.notifications()]` can return the
37
+ * `$boboddy_notifications_v1` field (e.g. via `Notify.inApp(...)`)
38
+ * without a type error.
33
39
  */
34
- fn: CodeStepFn<TInput["_output"], TResult["_output"]>;
40
+ fn: CodeStepFn<TInput["_output"], EffectiveResult<TResult["_output"], TFeatures>>;
35
41
  inputSchema?: TInput;
36
42
  resultSchema?: TResult;
37
43
  signals?: readonly CodeStepSignalSpec[];
44
+ /**
45
+ * Step features to attach — unlike `defineStep()`, only each feature's
46
+ * `_resultExtension`/`_signals` apply here (there is no prompt to append
47
+ * to on a `kind: "code"` step).
48
+ */
49
+ features?: TFeatures;
38
50
  status?: "draft" | "active";
39
51
  };
40
52
  /**
@@ -45,6 +57,6 @@ export type DefineCodeStepInput<TInput extends ZodType = ZodType, TResult extend
45
57
  * `entrypoint.fn` reference attached instead — see `StepDefinitionSpec`'s
46
58
  * own doc comment for what happens to it during collection.
47
59
  */
48
- export declare function codeStep<TInput extends ZodType = ZodType, TResult extends ZodType = ZodType, const TSignals extends ReadonlyArray<CodeStepSignalSpec> = never[]>(config: DefineCodeStepInput<TInput, TResult> & {
60
+ export declare function codeStep<TInput extends ZodType = ZodType, TResult extends ZodType = ZodType, const TSignals extends ReadonlyArray<CodeStepSignalSpec> = never[], const TFeatures extends ReadonlyArray<AnyStepFeature> = never[]>(config: DefineCodeStepInput<TInput, TResult, TFeatures> & {
49
61
  signals?: TSignals;
50
- }): TypedStepDefinitionSpec<TInput["_output"], TResult["_output"], SignalKeysOf<TSignals>, SignalTypeMapOf<TSignals, TResult["_output"]>>;
62
+ }): TypedStepDefinitionSpec<TInput["_output"], EffectiveResult<TResult["_output"], TFeatures>, SignalKeysOf<TSignals> | FeatureSignalKeys<TFeatures>, SignalTypeMapOf<TSignals, TResult["_output"]>>;
@@ -159,7 +159,7 @@ export type SignalTypeMapOf<TSignals extends readonly unknown[], TResult> = Pret
159
159
  sourcePath: infer P extends string;
160
160
  } ? TypeAtPath<TResult, P> : unknown;
161
161
  }>;
162
- type EffectiveResult<TResult, TFeatures extends ReadonlyArray<AnyStepFeature>> = Prettify<TResult & FeatureResultExtensions<TFeatures>>;
162
+ export type EffectiveResult<TResult, TFeatures extends ReadonlyArray<AnyStepFeature>> = Prettify<TResult & FeatureResultExtensions<TFeatures>>;
163
163
  type HasAdditionalInput<T> = 0 extends 1 & T ? boolean : [unknown] extends [T] ? false : true;
164
164
  export type TypedStepDefinitionSpec<TInput = unknown, TResult = unknown, TSignalKeys extends string = string, TSignalTypeMap extends Partial<Record<string, unknown>> = Record<string, unknown>> = StepDefinitionSpec & {
165
165
  readonly __inputType: TInput;
@@ -12082,6 +12082,12 @@ ${feature._promptAddition}` : feature._promptAddition;
12082
12082
  }
12083
12083
  // src/definitions/steps/define-code-step.ts
12084
12084
  function codeStep(config2) {
12085
+ const features = config2.features ?? [];
12086
+ let effectiveResult = config2.resultSchema;
12087
+ for (const feature of features) {
12088
+ effectiveResult = effectiveResult ? effectiveResult.extend(feature._resultExtension.shape) : feature._resultExtension;
12089
+ }
12090
+ const featureSignals = features.flatMap((f) => f._signals);
12085
12091
  const spec = {
12086
12092
  key: config2.key,
12087
12093
  name: config2.name,
@@ -12091,14 +12097,23 @@ function codeStep(config2) {
12091
12097
  status: config2.status ?? "active",
12092
12098
  prompt: null,
12093
12099
  inputSchemaJson: config2.inputSchema ? toJSONSchema(config2.inputSchema) : null,
12094
- resultSchemaJson: config2.resultSchema ? toJSONSchema(config2.resultSchema) : null,
12095
- signalExtractorDefinitions: (config2.signals ?? []).map((signal) => ({
12096
- key: signal.key ?? signal.sourcePath,
12097
- sourcePath: signal.sourcePath,
12098
- type: signal.type,
12099
- required: signal.required ?? true,
12100
- availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
12101
- })),
12100
+ resultSchemaJson: effectiveResult ? toJSONSchema(effectiveResult) : null,
12101
+ signalExtractorDefinitions: [
12102
+ ...(config2.signals ?? []).map((signal) => ({
12103
+ key: signal.key ?? signal.sourcePath,
12104
+ sourcePath: signal.sourcePath,
12105
+ type: signal.type,
12106
+ required: signal.required ?? true,
12107
+ availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
12108
+ })),
12109
+ ...featureSignals.map((signal) => ({
12110
+ key: signal.key,
12111
+ sourcePath: signal.sourcePath,
12112
+ type: signal.type,
12113
+ required: signal.required ?? true,
12114
+ availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
12115
+ }))
12116
+ ],
12102
12117
  opencodeMcpJson: null,
12103
12118
  opencodePluginJson: null,
12104
12119
  healthChecksJson: null,
@@ -13716,7 +13731,7 @@ var buildStepDefinitionsClient = (stepDefinitions) => {
13716
13731
  key: spec.key,
13717
13732
  name: spec.name,
13718
13733
  description: spec.description,
13719
- prompt: spec.prompt ?? "",
13734
+ prompt: spec.prompt ?? null,
13720
13735
  version: spec.version,
13721
13736
  kind: spec.kind,
13722
13737
  entrypointJson: spec.entrypointJson ?? null,
@@ -16084,20 +16099,29 @@ function date4(params) {
16084
16099
  // ../../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
16085
16100
  config(en_default());
16086
16101
  // src/definitions/steps/step-features.ts
16102
+ var notificationKindSchema = exports_external.enum([
16103
+ "feedback_request",
16104
+ "status_update",
16105
+ "blocked",
16106
+ "result_ready",
16107
+ "warning"
16108
+ ]);
16109
+ var notificationPrioritySchema = exports_external.enum(["low", "normal", "high", "urgent"]);
16110
+ var notificationChannelSchema = exports_external.enum([
16111
+ "in_app",
16112
+ "work_item_platform_comment",
16113
+ "email",
16114
+ "slack"
16115
+ ]);
16087
16116
  var NOTIFICATION_SIGNAL_KEY = "$boboddy_notifications_v1";
16088
16117
  var NOTIFICATION_RESULT_KEY = "$boboddy_notifications_v1";
16118
+ var DEFAULT_NOTIFICATION_KIND = "status_update";
16089
16119
  var notificationItemSchema = exports_external.object({
16090
- kind: exports_external.enum([
16091
- "feedback_request",
16092
- "status_update",
16093
- "blocked",
16094
- "result_ready",
16095
- "warning"
16096
- ]).describe("The kind of user notification."),
16120
+ kind: notificationKindSchema.describe("The kind of user notification."),
16097
16121
  title: exports_external.string().describe("Short, human-readable notification title."),
16098
16122
  body: exports_external.string().describe("The notification body / details."),
16099
- priority: exports_external.enum(["low", "normal", "high", "urgent"]).describe("How important this notification is for the user."),
16100
- suggestedChannels: exports_external.array(exports_external.enum(["in_app", "work_item_platform_comment", "email", "slack"])).optional().describe("Channels the agent thinks are worth using. The platform policy decides the final channels."),
16123
+ priority: notificationPrioritySchema.describe("How important this notification is for the user."),
16124
+ suggestedChannels: exports_external.array(notificationChannelSchema).optional().describe("Channels the agent thinks are worth using. The platform policy decides the final channels."),
16101
16125
  payload: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe('Kind-specific structured data. For "feedback_request": { category, urgency, suggestedKey? }.')
16102
16126
  }).describe("A single user notification emitted by the agent.");
16103
16127
  var notificationsFeature = {
@@ -16127,27 +16151,68 @@ var notificationsFeature = {
16127
16151
  }
16128
16152
  ]
16129
16153
  };
16154
+ var feedbackRequestsFeature = {
16155
+ _resultExtension: exports_external.object({
16156
+ [NOTIFICATION_RESULT_KEY]: exports_external.array(notificationItemSchema.extend({ kind: exports_external.literal("feedback_request") })).optional()
16157
+ }),
16158
+ _promptAddition: [
16159
+ "## Feedback Requests",
16160
+ "",
16161
+ `If you need to ask a human a clarifying question, populate the \`${NOTIFICATION_RESULT_KEY}\` array with items of kind \`"feedback_request"\`.`,
16162
+ "Each item must include:",
16163
+ "- **title**: A short, human-readable summary of the question.",
16164
+ "- **body**: The full question.",
16165
+ "- **priority**: One of `low`, `normal`, `high`, `urgent`.",
16166
+ '- **payload**: `{ "category": string, "urgency": "blocking"|"clarification"|"assumption"|"informational", "suggestedKey"?: string }`.'
16167
+ ].join(`
16168
+ `),
16169
+ _signals: notificationsFeature._signals
16170
+ };
16130
16171
  var Features = {
16131
- notifications: Object.assign(() => notificationsFeature, {
16132
- signal: {
16133
- key: NOTIFICATION_SIGNAL_KEY,
16134
- find(signals) {
16135
- const match = signals.find((s) => s.key === NOTIFICATION_SIGNAL_KEY);
16136
- if (!match)
16137
- return;
16138
- const parsed = exports_external.array(notificationItemSchema).safeParse(match.valueJson);
16139
- return parsed.success ? parsed.data : undefined;
16140
- }
16141
- }
16172
+ notifications: () => notificationsFeature,
16173
+ feedbackRequests: () => feedbackRequestsFeature
16174
+ };
16175
+ var NotificationSignal = {
16176
+ key: NOTIFICATION_SIGNAL_KEY,
16177
+ find(signals) {
16178
+ const match = signals.find((s) => s.key === NOTIFICATION_SIGNAL_KEY);
16179
+ if (!match)
16180
+ return;
16181
+ const parsed = exports_external.array(notificationItemSchema).safeParse(match.valueJson);
16182
+ return parsed.success ? parsed.data : undefined;
16183
+ }
16184
+ };
16185
+ var Notify = {
16186
+ create: (item) => ({
16187
+ [NOTIFICATION_RESULT_KEY]: [item]
16188
+ }),
16189
+ inApp: (title, body, priority, options) => Notify.create({
16190
+ kind: options?.kind ?? DEFAULT_NOTIFICATION_KIND,
16191
+ title,
16192
+ body,
16193
+ priority,
16194
+ suggestedChannels: ["in_app"],
16195
+ ...options?.payload ? { payload: options.payload } : {}
16142
16196
  }),
16143
- feedbackRequests: Object.assign(() => notificationsFeature, {
16144
- signal: {
16145
- key: NOTIFICATION_SIGNAL_KEY
16197
+ feedbackRequest: (question, category, urgency, suggestedKey) => Notify.create({
16198
+ kind: "feedback_request",
16199
+ title: question,
16200
+ body: question,
16201
+ priority: "normal",
16202
+ payload: {
16203
+ category,
16204
+ urgency,
16205
+ ...suggestedKey ? { suggestedKey } : {}
16146
16206
  }
16207
+ }),
16208
+ merge: (...fragments) => ({
16209
+ [NOTIFICATION_RESULT_KEY]: fragments.flatMap((fragment) => fragment[NOTIFICATION_RESULT_KEY])
16147
16210
  })
16148
16211
  };
16149
16212
  export {
16150
16213
  Features,
16214
+ NotificationSignal,
16215
+ Notify,
16151
16216
  codeStep,
16152
16217
  createPromptInputProxy,
16153
16218
  createPromptTemplateContext,
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import type { ZodObject, ZodRawShape } from "zod";
2
3
  type FeatureSignalSpec = {
3
4
  key: string;
@@ -17,24 +18,69 @@ export type AnyStepFeature = StepFeature;
17
18
  type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
18
19
  export type FeatureResultExtensions<TFeatures extends readonly AnyStepFeature[]> = [TFeatures[number]] extends [never] ? Record<never, never> : UnionToIntersection<TFeatures[number] extends StepFeature<infer R> ? R : never>;
19
20
  export type FeatureSignalKeys<TFeatures extends readonly AnyStepFeature[]> = NonNullable<TFeatures[number]["__signalKeys"]>;
20
- export type NotificationKind = "feedback_request" | "status_update" | "blocked" | "result_ready" | "warning";
21
- export type NotificationPriority = "low" | "normal" | "high" | "urgent";
22
- export type NotificationChannel = "in_app" | "work_item_platform_comment" | "email" | "slack";
21
+ declare const notificationKindSchema: z.ZodEnum<{
22
+ blocked: "blocked";
23
+ feedback_request: "feedback_request";
24
+ status_update: "status_update";
25
+ result_ready: "result_ready";
26
+ warning: "warning";
27
+ }>;
28
+ export type NotificationKind = z.infer<typeof notificationKindSchema>;
29
+ declare const notificationPrioritySchema: z.ZodEnum<{
30
+ high: "high";
31
+ low: "low";
32
+ normal: "normal";
33
+ urgent: "urgent";
34
+ }>;
35
+ export type NotificationPriority = z.infer<typeof notificationPrioritySchema>;
36
+ declare const notificationChannelSchema: z.ZodEnum<{
37
+ in_app: "in_app";
38
+ work_item_platform_comment: "work_item_platform_comment";
39
+ email: "email";
40
+ slack: "slack";
41
+ }>;
42
+ export type NotificationChannel = z.infer<typeof notificationChannelSchema>;
23
43
  export type FeedbackRequestUrgency = "blocking" | "clarification" | "assumption" | "informational";
24
- export type NotificationItem = {
25
- kind: NotificationKind;
26
- title: string;
27
- body: string;
28
- priority: NotificationPriority;
29
- suggestedChannels?: NotificationChannel[];
30
- /**
31
- * Kind-specific structured payload. For `feedback_request`:
32
- * `{ category, urgency, suggestedKey? }`.
33
- */
34
- payload?: Record<string, unknown>;
35
- };
36
44
  declare const NOTIFICATION_SIGNAL_KEY: "$boboddy_notifications_v1";
37
45
  declare const NOTIFICATION_RESULT_KEY: "$boboddy_notifications_v1";
46
+ declare const notificationItemSchema: z.ZodObject<{
47
+ kind: z.ZodEnum<{
48
+ blocked: "blocked";
49
+ feedback_request: "feedback_request";
50
+ status_update: "status_update";
51
+ result_ready: "result_ready";
52
+ warning: "warning";
53
+ }>;
54
+ title: z.ZodString;
55
+ body: z.ZodString;
56
+ priority: z.ZodEnum<{
57
+ high: "high";
58
+ low: "low";
59
+ normal: "normal";
60
+ urgent: "urgent";
61
+ }>;
62
+ suggestedChannels: z.ZodOptional<z.ZodArray<z.ZodEnum<{
63
+ in_app: "in_app";
64
+ work_item_platform_comment: "work_item_platform_comment";
65
+ email: "email";
66
+ slack: "slack";
67
+ }>>>;
68
+ payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
69
+ }, z.core.$strip>;
70
+ /** A single notification. Inferred from `notificationItemSchema` — the
71
+ * schema is the source of truth; this type can never drift from what's
72
+ * actually validated and pushed as JSON Schema. */
73
+ export type NotificationItem = z.infer<typeof notificationItemSchema>;
74
+ /**
75
+ * The shape `Notify.*` returns: a step result fragment carrying one or more
76
+ * notifications. Spread it into a larger result object, or return it
77
+ * directly if the notification *is* the step's whole result — either way it
78
+ * slots into the same `$boboddy_notifications_v1` field `Features.notifications()`
79
+ * wires a signal extractor for.
80
+ */
81
+ export type NotificationResultFragment = {
82
+ readonly [NOTIFICATION_RESULT_KEY]: NotificationItem[];
83
+ };
38
84
  type NotificationsFeature = StepFeature<{
39
85
  [NOTIFICATION_RESULT_KEY]?: NotificationItem[];
40
86
  }, typeof NOTIFICATION_SIGNAL_KEY>;
@@ -45,23 +91,38 @@ export type FeedbackRequestItem = {
45
91
  suggestedKey?: string;
46
92
  };
47
93
  export declare const Features: {
48
- readonly notifications: (() => NotificationsFeature) & {
49
- signal: {
50
- key: "$boboddy_notifications_v1";
51
- find(signals: Array<{
52
- key: string;
53
- valueJson: unknown;
54
- }>): NotificationItem[] | undefined;
55
- };
56
- };
94
+ readonly notifications: () => NotificationsFeature;
57
95
  /**
58
- * Convenience wrapper that emits `feedback_request` notifications.
59
- * Backed by the same `$boboddy_notifications_v1` signal.
96
+ * A real specialization of `notifications()`, not an alias: narrows every
97
+ * emitted item to `kind: "feedback_request"` and swaps in a
98
+ * feedback-request-specific prompt section. Backed by the same
99
+ * `$boboddy_notifications_v1` signal.
60
100
  */
61
- readonly feedbackRequests: (() => NotificationsFeature) & {
62
- signal: {
63
- key: "$boboddy_notifications_v1";
64
- };
65
- };
101
+ readonly feedbackRequests: () => NotificationsFeature;
102
+ };
103
+ export declare const NotificationSignal: {
104
+ readonly key: "$boboddy_notifications_v1";
105
+ readonly find: (signals: Array<{
106
+ key: string;
107
+ valueJson: unknown;
108
+ }>) => NotificationItem[] | undefined;
109
+ };
110
+ export declare const Notify: {
111
+ /** The one generic constructor. Field names match `NotificationItem`
112
+ * exactly, so a new optional field never forces a call-site rewrite. */
113
+ readonly create: (item: NotificationItem) => NotificationResultFragment;
114
+ /** Build a notification for the in-app inbox — the one channel the
115
+ * platform always delivers, so it's the safest default when the caller
116
+ * doesn't need a specific channel. */
117
+ readonly inApp: (title: string, body: string, priority: NotificationPriority, options?: {
118
+ kind?: NotificationKind;
119
+ payload?: Record<string, unknown>;
120
+ }) => NotificationResultFragment;
121
+ /** Build a `kind: "feedback_request"` notification — the value-builder
122
+ * counterpart to `Features.feedbackRequests()`. */
123
+ readonly feedbackRequest: (question: string, category: string, urgency: FeedbackRequestUrgency, suggestedKey?: string) => NotificationResultFragment;
124
+ /** Combine several notification result fragments (e.g. more than one
125
+ * `Notify.*` call) into a single result value. */
126
+ readonly merge: (...fragments: NotificationResultFragment[]) => NotificationResultFragment;
66
127
  };
67
128
  export {};
@@ -1,2 +1,4 @@
1
1
  export * from "./json-schema-paths";
2
2
  export * from "./validate-definition-specs";
3
+ export * from "./validate-input-bindings";
4
+ export * from "./validation-issue";