@boboddy/sdk 0.5.1 → 0.5.2

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.
@@ -0,0 +1,52 @@
1
+ export type DefinitionValidationIssue = {
2
+ readonly check: "signal-source-path" | "route-target" | "signal-binding" | "health-check-mcp-server" | "health-check-double-qualified" | "unbound-required-input" | "binding-target-field" | "binding-type-mismatch";
3
+ /**
4
+ * Whether this issue blocks a push. The 4 original checks are all
5
+ * unconditionally `"error"`, matching their implicit all-blocking
6
+ * behavior before this field existed. `"binding-type-mismatch"` is a
7
+ * `"warning"`-tier check — a resolved type disagreement is worth
8
+ * surfacing but, per §4's own bias, never provably a runtime failure the
9
+ * way an unresolved binding or a missing required input is.
10
+ * `"binding-target-field"`'s "unbound field name isn't a declared
11
+ * `additionalInput`" sub-check is `"info"`-tier — passing extra context
12
+ * a step doesn't declare as an `additionalInput` is allowed (the value is
13
+ * just dropped), so it's worth surfacing for awareness but never rises to
14
+ * even a warning. Only `assertValidDefinitionSpecs` treats these tiers
15
+ * differently: it blocks a push on `"error"` alone.
16
+ */
17
+ readonly severity: "error" | "warning" | "info";
18
+ readonly message: string;
19
+ /**
20
+ * The pipeline this issue belongs to, when the check is pipeline-scoped
21
+ * (`route-target`/`signal-binding`/the three Phase 2 binding checks) —
22
+ * absent for step-only checks (`signal-source-path`/`health-check-*`),
23
+ * which have no pipeline context of their own. Required before the
24
+ * designer (Phase 5) can attach an error to the right graph node.
25
+ */
26
+ readonly pipelineKey?: string;
27
+ /** The node this issue is about, when pipeline-scoped. */
28
+ readonly nodeKey?: string;
29
+ /**
30
+ * A second, related node this issue is about — e.g. `signal-binding`'s
31
+ * producer node, when different from `nodeKey`'s consumer. Absent when
32
+ * the issue is about a single node, or when the "other end" isn't a
33
+ * node in this pipeline at all (`route-target`'s target is a different
34
+ * *pipeline*, not a node).
35
+ */
36
+ readonly targetNodeKey?: string;
37
+ /**
38
+ * The specific `parallel` branch this issue is about, when `nodeKey`
39
+ * names a `parallel` node — one of that node's `branches` keys. Set only
40
+ * by the three Phase 2 binding checks (`unbound-required-input`/
41
+ * `binding-target-field`/`binding-type-mismatch`) when the
42
+ * `BindingContext` they're walking is a `parallel` branch's own bindings
43
+ * (`bindingContexts`' `ctx.branchKey`), not the node's own bindings.
44
+ * Absent for every other check, and absent for those three checks' own
45
+ * non-`parallel` (`step`/`fanOut`/`loop`) cases — without this, issues
46
+ * from two different branches of the same `parallel` node are
47
+ * indistinguishable by branch.
48
+ */
49
+ readonly branchKey?: string;
50
+ };
51
+ /** Formats a path list for an error message, capped so it stays readable. */
52
+ export declare function listPaths(paths: readonly string[], limit?: number): string;
@@ -4814,6 +4814,7 @@ export type PostApiPipelineDefinitionsData = {
4814
4814
  [key: string]: unknown;
4815
4815
  } | unknown;
4816
4816
  }>;
4817
+ entryNodeKey?: string;
4817
4818
  };
4818
4819
  path?: never;
4819
4820
  query?: never;
@@ -5260,6 +5261,7 @@ export type PutApiPipelineDefinitionsData = {
5260
5261
  [key: string]: unknown;
5261
5262
  } | unknown;
5262
5263
  }>;
5264
+ entryNodeKey?: string;
5263
5265
  };
5264
5266
  path?: never;
5265
5267
  query?: never;
package/dist/index.js CHANGED
@@ -16126,6 +16126,12 @@ ${feature._promptAddition}` : feature._promptAddition;
16126
16126
  }
16127
16127
  // src/definitions/steps/define-code-step.ts
16128
16128
  function codeStep(config2) {
16129
+ const features = config2.features ?? [];
16130
+ let effectiveResult = config2.resultSchema;
16131
+ for (const feature of features) {
16132
+ effectiveResult = effectiveResult ? effectiveResult.extend(feature._resultExtension.shape) : feature._resultExtension;
16133
+ }
16134
+ const featureSignals = features.flatMap((f) => f._signals);
16129
16135
  const spec = {
16130
16136
  key: config2.key,
16131
16137
  name: config2.name,
@@ -16135,14 +16141,23 @@ function codeStep(config2) {
16135
16141
  status: config2.status ?? "active",
16136
16142
  prompt: null,
16137
16143
  inputSchemaJson: config2.inputSchema ? toJSONSchema(config2.inputSchema) : null,
16138
- resultSchemaJson: config2.resultSchema ? toJSONSchema(config2.resultSchema) : null,
16139
- signalExtractorDefinitions: (config2.signals ?? []).map((signal) => ({
16140
- key: signal.key ?? signal.sourcePath,
16141
- sourcePath: signal.sourcePath,
16142
- type: signal.type,
16143
- required: signal.required ?? true,
16144
- availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
16145
- })),
16144
+ resultSchemaJson: effectiveResult ? toJSONSchema(effectiveResult) : null,
16145
+ signalExtractorDefinitions: [
16146
+ ...(config2.signals ?? []).map((signal) => ({
16147
+ key: signal.key ?? signal.sourcePath,
16148
+ sourcePath: signal.sourcePath,
16149
+ type: signal.type,
16150
+ required: signal.required ?? true,
16151
+ availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
16152
+ })),
16153
+ ...featureSignals.map((signal) => ({
16154
+ key: signal.key,
16155
+ sourcePath: signal.sourcePath,
16156
+ type: signal.type,
16157
+ required: signal.required ?? true,
16158
+ availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
16159
+ }))
16160
+ ],
16146
16161
  opencodeMcpJson: null,
16147
16162
  opencodePluginJson: null,
16148
16163
  healthChecksJson: null,
@@ -16205,20 +16220,29 @@ var buildStepDefinitionsClient = (stepDefinitions) => {
16205
16220
  };
16206
16221
  };
16207
16222
  // src/definitions/steps/step-features.ts
16223
+ var notificationKindSchema = exports_external.enum([
16224
+ "feedback_request",
16225
+ "status_update",
16226
+ "blocked",
16227
+ "result_ready",
16228
+ "warning"
16229
+ ]);
16230
+ var notificationPrioritySchema = exports_external.enum(["low", "normal", "high", "urgent"]);
16231
+ var notificationChannelSchema = exports_external.enum([
16232
+ "in_app",
16233
+ "work_item_platform_comment",
16234
+ "email",
16235
+ "slack"
16236
+ ]);
16208
16237
  var NOTIFICATION_SIGNAL_KEY = "$boboddy_notifications_v1";
16209
16238
  var NOTIFICATION_RESULT_KEY = "$boboddy_notifications_v1";
16239
+ var DEFAULT_NOTIFICATION_KIND = "status_update";
16210
16240
  var notificationItemSchema = exports_external.object({
16211
- kind: exports_external.enum([
16212
- "feedback_request",
16213
- "status_update",
16214
- "blocked",
16215
- "result_ready",
16216
- "warning"
16217
- ]).describe("The kind of user notification."),
16241
+ kind: notificationKindSchema.describe("The kind of user notification."),
16218
16242
  title: exports_external.string().describe("Short, human-readable notification title."),
16219
16243
  body: exports_external.string().describe("The notification body / details."),
16220
- priority: exports_external.enum(["low", "normal", "high", "urgent"]).describe("How important this notification is for the user."),
16221
- 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."),
16244
+ priority: notificationPrioritySchema.describe("How important this notification is for the user."),
16245
+ suggestedChannels: exports_external.array(notificationChannelSchema).optional().describe("Channels the agent thinks are worth using. The platform policy decides the final channels."),
16222
16246
  payload: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe('Kind-specific structured data. For "feedback_request": { category, urgency, suggestedKey? }.')
16223
16247
  }).describe("A single user notification emitted by the agent.");
16224
16248
  var notificationsFeature = {
@@ -16248,23 +16272,62 @@ var notificationsFeature = {
16248
16272
  }
16249
16273
  ]
16250
16274
  };
16275
+ var feedbackRequestsFeature = {
16276
+ _resultExtension: exports_external.object({
16277
+ [NOTIFICATION_RESULT_KEY]: exports_external.array(notificationItemSchema.extend({ kind: exports_external.literal("feedback_request") })).optional()
16278
+ }),
16279
+ _promptAddition: [
16280
+ "## Feedback Requests",
16281
+ "",
16282
+ `If you need to ask a human a clarifying question, populate the \`${NOTIFICATION_RESULT_KEY}\` array with items of kind \`"feedback_request"\`.`,
16283
+ "Each item must include:",
16284
+ "- **title**: A short, human-readable summary of the question.",
16285
+ "- **body**: The full question.",
16286
+ "- **priority**: One of `low`, `normal`, `high`, `urgent`.",
16287
+ '- **payload**: `{ "category": string, "urgency": "blocking"|"clarification"|"assumption"|"informational", "suggestedKey"?: string }`.'
16288
+ ].join(`
16289
+ `),
16290
+ _signals: notificationsFeature._signals
16291
+ };
16251
16292
  var Features = {
16252
- notifications: Object.assign(() => notificationsFeature, {
16253
- signal: {
16254
- key: NOTIFICATION_SIGNAL_KEY,
16255
- find(signals) {
16256
- const match = signals.find((s) => s.key === NOTIFICATION_SIGNAL_KEY);
16257
- if (!match)
16258
- return;
16259
- const parsed = exports_external.array(notificationItemSchema).safeParse(match.valueJson);
16260
- return parsed.success ? parsed.data : undefined;
16261
- }
16262
- }
16293
+ notifications: () => notificationsFeature,
16294
+ feedbackRequests: () => feedbackRequestsFeature
16295
+ };
16296
+ var NotificationSignal = {
16297
+ key: NOTIFICATION_SIGNAL_KEY,
16298
+ find(signals) {
16299
+ const match = signals.find((s) => s.key === NOTIFICATION_SIGNAL_KEY);
16300
+ if (!match)
16301
+ return;
16302
+ const parsed = exports_external.array(notificationItemSchema).safeParse(match.valueJson);
16303
+ return parsed.success ? parsed.data : undefined;
16304
+ }
16305
+ };
16306
+ var Notify = {
16307
+ create: (item) => ({
16308
+ [NOTIFICATION_RESULT_KEY]: [item]
16309
+ }),
16310
+ inApp: (title, body, priority, options) => Notify.create({
16311
+ kind: options?.kind ?? DEFAULT_NOTIFICATION_KIND,
16312
+ title,
16313
+ body,
16314
+ priority,
16315
+ suggestedChannels: ["in_app"],
16316
+ ...options?.payload ? { payload: options.payload } : {}
16263
16317
  }),
16264
- feedbackRequests: Object.assign(() => notificationsFeature, {
16265
- signal: {
16266
- key: NOTIFICATION_SIGNAL_KEY
16318
+ feedbackRequest: (question, category, urgency, suggestedKey) => Notify.create({
16319
+ kind: "feedback_request",
16320
+ title: question,
16321
+ body: question,
16322
+ priority: "normal",
16323
+ payload: {
16324
+ category,
16325
+ urgency,
16326
+ ...suggestedKey ? { suggestedKey } : {}
16267
16327
  }
16328
+ }),
16329
+ merge: (...fragments) => ({
16330
+ [NOTIFICATION_RESULT_KEY]: fragments.flatMap((fragment) => fragment[NOTIFICATION_RESULT_KEY])
16268
16331
  })
16269
16332
  };
16270
16333
  // src/definitions/advancement-policies/define-advancement-policy.ts
@@ -16961,25 +17024,6 @@ function compileLoopState(stateKey, state, ctx) {
16961
17024
  function compileTerminalState(stateKey, kind) {
16962
17025
  return { nodeDefinitions: [{ nodeKey: stateKey, kind }], edges: [] };
16963
17026
  }
16964
- function assertNoIllegalConvergentEdges(pipelineKey, nodeKindByKey, edges) {
16965
- const incoming = new Map;
16966
- for (const edge of edges) {
16967
- const list = incoming.get(edge.toNodeKey) ?? [];
16968
- list.push(edge);
16969
- incoming.set(edge.toNodeKey, list);
16970
- }
16971
- for (const [targetKey, incomingEdges] of incoming) {
16972
- if (incomingEdges.length <= 1)
16973
- continue;
16974
- const hasInvalidSource = incomingEdges.some((edge) => {
16975
- const kind = nodeKindByKey.get(edge.fromNodeKey);
16976
- return kind !== "choice" && kind !== "loop";
16977
- });
16978
- if (hasInvalidSource) {
16979
- 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).`);
16980
- }
16981
- }
16982
- }
16983
17027
 
16984
17028
  // src/definitions/pipelines/define-pipeline.ts
16985
17029
  function isWorkingNodeDefinition(node) {
@@ -17016,8 +17060,6 @@ function definePipeline(config2) {
17016
17060
  nodeDefinitions.push(...compiled.nodeDefinitions);
17017
17061
  dependencyEdges.push(...compiled.edges);
17018
17062
  }
17019
- const nodeKindByKey = new Map(nodeDefinitions.map((node) => [node.nodeKey, node.kind]));
17020
- assertNoIllegalConvergentEdges(config2.key, nodeKindByKey, dependencyEdges);
17021
17063
  let inputSchemaJson = null;
17022
17064
  if (config2.input) {
17023
17065
  try {
@@ -17033,6 +17075,7 @@ function definePipeline(config2) {
17033
17075
  version: config2.version ?? 1,
17034
17076
  status: config2.status ?? "active",
17035
17077
  inputSchemaJson,
17078
+ entryNodeKey: config2.startAt,
17036
17079
  _stepDefinitions: [...stepDefMap.values()],
17037
17080
  nodeDefinitions,
17038
17081
  dependencyEdges
@@ -17145,6 +17188,7 @@ var buildPipelineDefinitionsClient = (pipelineDefinitions) => {
17145
17188
  description: spec.description,
17146
17189
  status: spec.status,
17147
17190
  inputSchemaJson: spec.inputSchemaJson,
17191
+ entryNodeKey: spec.entryNodeKey,
17148
17192
  nodeDefinitions,
17149
17193
  dependencyEdges
17150
17194
  };
@@ -17624,6 +17668,8 @@ export {
17624
17668
  Features,
17625
17669
  GitHubIntegrations,
17626
17670
  NotificationRules,
17671
+ NotificationSignal,
17672
+ Notify,
17627
17673
  PipelineDefinitions,
17628
17674
  PipelineExecutions,
17629
17675
  ProjectContext,