@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.
- package/dist/definitions/pipelines/chain-graph.d.ts +29 -0
- package/dist/definitions/pipelines/compile-node-definitions.d.ts +0 -8
- package/dist/definitions/pipelines/define-pipeline.d.ts +2 -0
- package/dist/definitions/pipelines/index.js +2 -21
- package/dist/definitions/steps/define-code-step.d.ts +17 -5
- package/dist/definitions/steps/define-step.d.ts +1 -1
- package/dist/definitions/steps/index.js +97 -32
- package/dist/definitions/steps/step-features.d.ts +92 -31
- package/dist/definitions/validation/index.d.ts +2 -0
- package/dist/definitions/validation/index.js +394 -32
- package/dist/definitions/validation/json-schema-paths.d.ts +29 -0
- package/dist/definitions/validation/validate-definition-specs.d.ts +12 -23
- package/dist/definitions/validation/validate-input-bindings.d.ts +7 -0
- package/dist/definitions/validation/validation-issue.d.ts +52 -0
- package/dist/generated/types.gen.d.ts +2 -0
- package/dist/index.js +99 -53
- package/dist/push/index.js +484 -64
- package/package.json +1 -1
|
@@ -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:
|
|
12095
|
-
signalExtractorDefinitions:
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
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:
|
|
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:
|
|
16100
|
-
suggestedChannels: exports_external.array(
|
|
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:
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
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
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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: (
|
|
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
|
-
*
|
|
59
|
-
*
|
|
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: (
|
|
62
|
-
|
|
63
|
-
|
|
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 {};
|