@intentius/chant-lexicon-temporal 0.1.6 → 0.1.8

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.
@@ -75,7 +75,7 @@ function generateWorkflow(config: OpConfig): string {
75
75
  const lines: string[] = [
76
76
  "// Generated by chant — do not edit directly.",
77
77
  `// Source: ${config.name}.op.ts`,
78
- "import { proxyActivities, condition, defineSignal, setHandler } from '@temporalio/workflow';",
78
+ "import { proxyActivities, condition, defineSignal, setHandler, upsertSearchAttributes } from '@temporalio/workflow';",
79
79
  "import { TEMPORAL_ACTIVITY_PROFILES } from '@intentius/chant-lexicon-temporal';",
80
80
  "import type * as activities from './activities';",
81
81
  "",
@@ -107,29 +107,91 @@ function generateWorkflow(config: OpConfig): string {
107
107
  // Workflow function
108
108
  lines.push(`export async function ${fnName}(): Promise<void> {`);
109
109
 
110
+ // Initial search attributes — OpName plus any user-provided attrs.
111
+ // Each value is wrapped in a single-element array (classic
112
+ // upsertSearchAttributes API takes arrays).
113
+ const initialAttrs: Record<string, string[]> = {
114
+ OpName: [config.name],
115
+ };
116
+ for (const [k, v] of Object.entries(config.searchAttributes ?? {})) {
117
+ initialAttrs[k] = [v];
118
+ }
119
+ lines.push(` upsertSearchAttributes(${JSON.stringify(initialAttrs)});`);
120
+ lines.push("");
121
+
122
+ // Counter for outcome-attribute capture variables (workflow-scoped).
123
+ let resultCounter = 0;
124
+ const nextResultVar = (): string => `__r${resultCounter++}`;
125
+
126
+ // Build a `String(<var>?.<from-path>)` fragment from a dot-path.
127
+ const stringifyFromPath = (varName: string, from?: string): string => {
128
+ if (!from) return `String(${varName})`;
129
+ const parts = from.split(".");
130
+ return `String(${varName}?.${parts.join("?.")})`;
131
+ };
132
+
133
+ // Emit `upsertSearchAttributes({ <name>: [<expr>] })` for an outcome attr.
134
+ const emitOutcomeUpsert = (
135
+ step: ActivityStep,
136
+ varName: string,
137
+ indent = " ",
138
+ ): string | null => {
139
+ if (!step.outcomeAttribute) return null;
140
+ const { name, from } = step.outcomeAttribute;
141
+ return `${indent}upsertSearchAttributes({ ${JSON.stringify(name)}: [${stringifyFromPath(varName, from)}] });`;
142
+ };
143
+
110
144
  const renderPhases = (phases: PhaseDefinition[]) => {
111
145
  for (const phase of phases) {
112
146
  const phaseLines: string[] = [];
113
147
  phaseLines.push(` // Phase: ${phase.name}`);
148
+ phaseLines.push(` upsertSearchAttributes({ Phase: ${JSON.stringify([phase.name])} });`);
114
149
 
115
150
  const activitySteps = phase.steps.filter(isActivityStep);
116
151
  const gateSteps = phase.steps.filter(isGateStep);
117
152
 
118
153
  if (phase.parallel && activitySteps.length > 1) {
119
- phaseLines.push(" await Promise.all([");
120
- for (const step of activitySteps) {
121
- const argsStr = step.args && Object.keys(step.args).length > 0
122
- ? JSON.stringify(step.args)
123
- : "{}";
124
- phaseLines.push(` ${step.fn}(${argsStr}),`);
154
+ // Capture results into an array if any step has an outcome attribute,
155
+ // otherwise just await Promise.all without the destructure.
156
+ const anyOutcome = activitySteps.some((s) => s.outcomeAttribute);
157
+ if (anyOutcome) {
158
+ const vars = activitySteps.map(() => nextResultVar());
159
+ phaseLines.push(` const [${vars.join(", ")}] = await Promise.all([`);
160
+ for (let i = 0; i < activitySteps.length; i++) {
161
+ const step = activitySteps[i];
162
+ const argsStr = step.args && Object.keys(step.args).length > 0
163
+ ? JSON.stringify(step.args)
164
+ : "{}";
165
+ phaseLines.push(` ${step.fn}(${argsStr}),`);
166
+ }
167
+ phaseLines.push(" ]);");
168
+ for (let i = 0; i < activitySteps.length; i++) {
169
+ const upsert = emitOutcomeUpsert(activitySteps[i], vars[i]);
170
+ if (upsert) phaseLines.push(upsert);
171
+ }
172
+ } else {
173
+ phaseLines.push(" await Promise.all([");
174
+ for (const step of activitySteps) {
175
+ const argsStr = step.args && Object.keys(step.args).length > 0
176
+ ? JSON.stringify(step.args)
177
+ : "{}";
178
+ phaseLines.push(` ${step.fn}(${argsStr}),`);
179
+ }
180
+ phaseLines.push(" ]);");
125
181
  }
126
- phaseLines.push(" ]);");
127
182
  } else {
128
183
  for (const step of activitySteps) {
129
184
  const argsStr = step.args && Object.keys(step.args).length > 0
130
185
  ? JSON.stringify(step.args)
131
186
  : "{}";
132
- phaseLines.push(` await ${step.fn}(${argsStr});`);
187
+ if (step.outcomeAttribute) {
188
+ const v = nextResultVar();
189
+ phaseLines.push(` const ${v} = await ${step.fn}(${argsStr});`);
190
+ const upsert = emitOutcomeUpsert(step, v);
191
+ if (upsert) phaseLines.push(upsert);
192
+ } else {
193
+ phaseLines.push(` await ${step.fn}(${argsStr});`);
194
+ }
133
195
  }
134
196
  }
135
197
 
@@ -32,7 +32,7 @@ describe("temporal plugin", () => {
32
32
  const tools = temporalPlugin.mcpTools?.();
33
33
  expect(Array.isArray(tools)).toBe(true);
34
34
  expect(tools?.length).toBe(1);
35
- expect(tools?.[0].name).toBe("diff");
35
+ expect(tools?.[0].name).toBe("temporal:diff");
36
36
  });
37
37
 
38
38
  it("mcpResources() returns at least 2 resources including resource-catalog", () => {
@@ -40,7 +40,7 @@ describe("temporal plugin", () => {
40
40
  expect(Array.isArray(resources)).toBe(true);
41
41
  expect((resources?.length ?? 0)).toBeGreaterThanOrEqual(2);
42
42
  const uris = resources?.map((r) => r.uri);
43
- expect(uris).toContain("resource-catalog");
43
+ expect(uris).toContain("temporal:resource-catalog");
44
44
  });
45
45
 
46
46
  it("skills() returns 2 skill entries", () => {
package/src/plugin.ts CHANGED
@@ -103,6 +103,7 @@ export const temporalPlugin: LexiconPlugin = {
103
103
  createDiffTool(
104
104
  temporalSerializer,
105
105
  "Compare current Temporal build output (docker-compose.yml, temporal-setup.sh, schedules/) against previous version",
106
+ "temporal",
106
107
  ),
107
108
  ];
108
109
  },
@@ -114,6 +115,7 @@ export const temporalPlugin: LexiconPlugin = {
114
115
  "Temporal Resource Types",
115
116
  "All supported Temporal resource types: TemporalServer, TemporalNamespace, SearchAttribute, TemporalSchedule",
116
117
  "lexicon-temporal.json",
118
+ "temporal",
117
119
  ),
118
120
  {
119
121
  uri: "examples/dev-server",
@@ -283,4 +285,9 @@ export const temporalPlugin: LexiconPlugin = {
283
285
  const { generateDocs } = await import("./codegen/docs");
284
286
  return generateDocs(options);
285
287
  },
288
+
289
+ async describeResources(options) {
290
+ const { describeResources } = await import("./describe-resources");
291
+ return describeResources(options);
292
+ },
286
293
  };