agentic-workflow-manager 9.10.0 → 9.10.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.
@@ -183,10 +183,39 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
183
183
  if (!roles.includes(options.role))
184
184
  throw new Error('--role is invalid');
185
185
  const runtime = (0, capabilities_1.validateRuntimeKey)({ target: options.provider, kind: options.runtimeKind, version: options.runtimeVersion, accountScopeDigest: options.accountScopeDigest });
186
- const result = report.schema === 'compact-slices/v1' && !options.optInV1 ? (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, policy: undefined, capabilities: undefined, runtime, now: new Date(), optInV1: false }) : (() => { const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(cwd); const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, new Date()); if (!options.lineage)
187
- return (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, sliceId: options.slice, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date(), optInV1: options.optInV1 === true }); const state = journalObservation(cwd).journalState; const lineage = state.implementationLineages.find(candidate => candidate.id === options.lineage); const next = (0, journal_1.resolveLineageEscalation)(state, options.lineage, lineage.initialProfile); const resolved = (0, resolve_1.resolveSelection)({ role: 'implementer', requestedProfile: next.profile, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date() }); if (resolved.state === 'blocked')
188
- return resolved; const highRequiresFull = next.effort !== 'high' || (policy.state === 'approved' && (() => { const full = policy.policy.content.mappings.find(row => row.target === runtime.target && row.runtimeKind === runtime.kind)?.fullCapability; return full?.selector.kind === resolved.selection.selector.kind && full.selector.id === resolved.selection.selector.id && full.effort.kind === 'explicit' && full.effort.value === 'high'; })()); if (resolved.selection.effort.kind !== 'explicit' || resolved.selection.effort.value !== next.effort || !highRequiresFull)
189
- return { state: 'blocked', diagnostics: [{ code: 'ROUTING_ESCALATION_EFFORT_UNAVAILABLE', message: 'The approved selected effort does not match the next lineage escalation.' }] }; return { ...resolved, envelope: { schema: 'routing-envelope/v1', runtime, role: 'implementer', planDigest: report.planDigest, executionDigest: report.executionDigest, sliceId: lineage.sliceId, requestedProfile: lineage.initialProfile, effectiveProfile: next.profile, resolved: resolved.selection, policyDigest: resolved.policyDigest, capabilityDigest: resolved.capabilityDigest, outcome: resolved.outcome, unavailableEvidence: resolved.unavailableEvidence }, custodyHandoff: { kind: 'routing-reserve', obligationId: lineage.obligationId, lineageId: lineage.id } }; })();
186
+ const result = report.schema === 'compact-slices/v1' && !options.optInV1 ? (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, policy: undefined, capabilities: undefined, runtime, now: new Date(), optInV1: false }) : (() => {
187
+ const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(cwd);
188
+ const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, new Date());
189
+ if (!options.lineage) {
190
+ const first = (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, sliceId: options.slice, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date(), optInV1: options.optInV1 === true });
191
+ // The consumer forwards THIS envelope to `job routing-reserve`, which
192
+ // is what creates the lineage the --lineage escalation path below
193
+ // then reads. Emitting it only there made the first attempt of any
194
+ // lineage unreservable: envelope <= lineage <= reserve <= envelope.
195
+ // Every field comes from values already in hand, so the CLI stays the
196
+ // sole author and the consumer never assembles a protocol document.
197
+ // No custodyHandoff here on purpose: the obligation and lineage ids
198
+ // are the caller's to choose on a first attempt, and inventing them
199
+ // would put identity the CLI cannot know into custody.
200
+ if (first.state !== 'resolved' || report.schema !== 'compact-slices/v2' || report.executionDigest === undefined)
201
+ return first;
202
+ const slice = report.manifest.slices.find((candidate) => candidate.id === options.slice);
203
+ const requestedProfile = options.role === 'implementer' ? slice?.implementerProfile : 'full';
204
+ if (requestedProfile === undefined)
205
+ return first;
206
+ return { ...first, envelope: { schema: 'routing-envelope/v1', runtime, role: options.role, planDigest: report.planDigest, executionDigest: report.executionDigest, ...(options.slice === undefined ? {} : { sliceId: options.slice }), requestedProfile, effectiveProfile: first.effectiveProfile, resolved: first.selection, policyDigest: first.policyDigest, capabilityDigest: first.capabilityDigest, outcome: first.outcome, unavailableEvidence: first.unavailableEvidence } };
207
+ }
208
+ const state = journalObservation(cwd).journalState;
209
+ const lineage = state.implementationLineages.find(candidate => candidate.id === options.lineage);
210
+ const next = (0, journal_1.resolveLineageEscalation)(state, options.lineage, lineage.initialProfile);
211
+ const resolved = (0, resolve_1.resolveSelection)({ role: 'implementer', requestedProfile: next.profile, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date() });
212
+ if (resolved.state === 'blocked')
213
+ return resolved;
214
+ const highRequiresFull = next.effort !== 'high' || (policy.state === 'approved' && (() => { const full = policy.policy.content.mappings.find(row => row.target === runtime.target && row.runtimeKind === runtime.kind)?.fullCapability; return full?.selector.kind === resolved.selection.selector.kind && full.selector.id === resolved.selection.selector.id && full.effort.kind === 'explicit' && full.effort.value === 'high'; })());
215
+ if (resolved.selection.effort.kind !== 'explicit' || resolved.selection.effort.value !== next.effort || !highRequiresFull)
216
+ return { state: 'blocked', diagnostics: [{ code: 'ROUTING_ESCALATION_EFFORT_UNAVAILABLE', message: 'The approved selected effort does not match the next lineage escalation.' }] };
217
+ return { ...resolved, envelope: { schema: 'routing-envelope/v1', runtime, role: 'implementer', planDigest: report.planDigest, executionDigest: report.executionDigest, sliceId: lineage.sliceId, requestedProfile: lineage.initialProfile, effectiveProfile: next.profile, resolved: resolved.selection, policyDigest: resolved.policyDigest, capabilityDigest: resolved.capabilityDigest, outcome: resolved.outcome, unavailableEvidence: resolved.unavailableEvidence }, custodyHandoff: { kind: 'routing-reserve', obligationId: lineage.obligationId, lineageId: lineage.id } };
218
+ })();
190
219
  process.stdout.write(options.json ? `${JSON.stringify(result)}\n` : `Plan routing: ${result.state}\n`);
191
220
  if (result.state === 'blocked')
192
221
  process.exitCode = 2;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Supervisor = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
3
+ exports.Supervisor = exports.MAX_SENSOR_DEFERRALS = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
4
4
  exports.runSupervisorLoop = runSupervisorLoop;
5
5
  const store_1 = require("../../core/journal/store");
6
6
  const fingerprint_1 = require("../../core/journal/fingerprint");
@@ -120,6 +120,15 @@ exports.DEFAULT_SUPERVISOR_CONFIG = {
120
120
  jobStallObservationMs: 5 * 60000, // R3.5 default: mismo orden de magnitud que heartbeatTimeoutMs, concern independiente
121
121
  maxParallelTracks: 1, // overridden en tiempo de ejecución con loadDefaultParallelism() (watch/index.ts)
122
122
  };
123
+ // 'frozen' (R5.2/R6.3, Task 10): SOLO puede ocurrir en el journal de un
124
+ // TRACK individual (nunca en el del plan) — el track terminó, con las 6
125
+ // observaciones demostrables (cero jobs vivos, gate local verde, worktree
126
+ // limpio, generación propia terminada con identidad confirmada), de
127
+ // atender un `track-freeze-request` pedido por el supervisor del plan.
128
+ /** How many consecutive ticks an inconclusive sensor verdict may defer dispatch
129
+ * before the supervisor says so out loud. Small: the deferral covers a verdict
130
+ * still settling, not one that never will. */
131
+ exports.MAX_SENSOR_DEFERRALS = 3;
123
132
  const LIVE = ['received', 'spawn-intent', 'claimed', 'running', 'cancel-requested'];
124
133
  function recoveryWhitelistBlocker(state) {
125
134
  if (state.cycle.status === 'BLOCKED')
@@ -149,6 +158,10 @@ class Supervisor {
149
158
  dispatchAdmission;
150
159
  backoff = new generations_1.Backoff();
151
160
  relaunchNotBefore = 0;
161
+ /** Consecutive ticks deferred on an inconclusive sensor verdict. Bounded:
162
+ * the deferral exists for a verdict that is still settling, and a verdict
163
+ * that never settles must become visible rather than loop forever. */
164
+ sensorDeferrals = 0;
152
165
  lastActivity = null;
153
166
  lastGenerationToken = null;
154
167
  trackRuntime;
@@ -226,12 +239,36 @@ class Supervisor {
226
239
  // the already-started controller is still settling. It never
227
240
  // authorizes dispatch; retry the read-only admission next tick
228
241
  // instead of permanently custodying a healthy cycle.
242
+ //
243
+ // Bounded, because the verdict is not always transient: a
244
+ // registry whose sensors are all disabled reports
245
+ // `not-certified` forever. Unbounded, this returned before
246
+ // `consumePendingRequests` below, so the supervisor ticked
247
+ // indefinitely applying nothing and writing nothing — no event,
248
+ // no state change, no exit. Refusing to progress is correct
249
+ // here; refusing invisibly is not. Consuming requests cannot be
250
+ // hoisted above this gate to compensate: applying a job-request
251
+ // creates a Job that runnerTick can start, which is the dispatch
252
+ // this admission exists to authorize.
229
253
  if (admission.planState === 'valid' && admission.currentness === 'current'
230
- && admission.sensors === 'not-certified' && admission.journal === 'not-required')
231
- return 'continue';
254
+ && admission.sensors === 'not-certified' && admission.journal === 'not-required') {
255
+ this.sensorDeferrals += 1;
256
+ if (this.sensorDeferrals <= exports.MAX_SENSOR_DEFERRALS)
257
+ return 'continue';
258
+ // The reason must not carry the running counter. enterCustody is
259
+ // idempotent on (status, reason), so a message that changes every
260
+ // tick defeats that guard and rewrites the journal plus appends a
261
+ // custody-blocked event every tickMs, without bound. Naming the
262
+ // bound instead of the count keeps the reason stable, so custody
263
+ // is entered once and stays entered.
264
+ (0, generations_1.enterCustody)(this.repoRoot, this.branch, `veredicto de sensores no concluyente tras ${exports.MAX_SENSOR_DEFERRALS} ticks: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
265
+ return 'custody';
266
+ }
232
267
  (0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión compacta desatendida bloqueada antes de dispatch: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
233
268
  return 'custody';
234
269
  }
270
+ // The verdict settled, which is exactly what the deferral waited for.
271
+ this.sensorDeferrals = 0;
235
272
  }
236
273
  let recoveryResumeAction;
237
274
  // Schema-2 custody is reconciled before any controller launch. This is
@@ -13,12 +13,20 @@ const plan_1 = require("../../../src/commands/plan");
13
13
  const init_1 = require("../../../src/commands/watch/init");
14
14
  const providers_1 = require("../../../src/providers");
15
15
  const registry_1 = require("../../../src/core/renderers/registry");
16
+ const canonical_1 = require("../../../src/core/model-policy/canonical");
17
+ const capabilities_1 = require("../../../src/core/model-policy/capabilities");
18
+ const journal_1 = require("../../../src/core/model-policy/journal");
19
+ const types_1 = require("../../../src/core/journal/types");
16
20
  const stdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
17
21
  const repositoryRoot = path_1.default.resolve(__dirname, '../../../..');
18
22
  const approved = (0, validate_1.validatePlanFile)('docs/plans/2026-09-14-compact-only-bootstrap-plan.md', repositoryRoot);
19
23
  if (approved.state !== 'valid')
20
24
  throw new Error(`tracked bootstrap plan must validate before command tests: ${approved.state}`);
21
25
  const valid = approved;
26
+ const approvedV2 = (0, validate_1.validatePlanFile)('valid.md', path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v2'));
27
+ if (approvedV2.state !== 'valid')
28
+ throw new Error(`v2 fixture must validate before command tests: ${approvedV2.state}`);
29
+ const v2Valid = approvedV2;
22
30
  const invalid = {
23
31
  state: 'invalid', diagnostics: [{ code: 'PLAN_MARKERS', message: 'compact markers must occur once in order' }],
24
32
  };
@@ -60,6 +68,56 @@ describe('plan validate Commander wiring', () => {
60
68
  expect(policy).not.toHaveBeenCalled();
61
69
  expect(capabilities).not.toHaveBeenCalled();
62
70
  });
71
+ // The consumer reference says: call `plan resolve` for the exact role and
72
+ // local slice, then send ITS frozen envelope to `job routing-reserve`. The
73
+ // envelope was only ever built on the --lineage branch, which resolves the
74
+ // lineage out of journal state — and lineages are created by
75
+ // reserveRoutingAttempt, i.e. by routing-reserve itself. envelope <= lineage
76
+ // <= reserve <= envelope, so the first attempt of any lineage could never be
77
+ // reserved. This test goes from resolve output to a reserved attempt with no
78
+ // hand-built envelope literal, which is the only shape that can catch it:
79
+ // tests/commands/job/routing.test.ts passes a literal and so starts one step
80
+ // past the gap.
81
+ it('emits a reservable envelope on a first v2 attempt, closing the resolve to reserve path', async () => {
82
+ // A genuinely validated report: the resolver rejects any object that did
83
+ // not come out of validatePlanFile, so a spread-and-relabel fixture
84
+ // cannot reach this path at all.
85
+ const sliceId = 'S1';
86
+ const sel = { selector: { kind: 'model', id: 'gpt-5.6-luna' }, effort: { kind: 'explicit', value: 'medium' } };
87
+ const full = { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'high' } };
88
+ const sha = 'a'.repeat(64);
89
+ const content = { schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: { mechanical: sel, integration: sel, judgment: sel }, fullCapability: full, degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } };
90
+ const readEffectivePolicy = () => ({ state: 'approved', provenance: 'user', policy: { schema: 'approved-model-policy/v1', content, contentDigest: (0, canonical_1.canonicalPolicyDigest)(content), approval: { approvedAt: '2026-09-19T00:00:00.000Z', approvalId: 'a1' }, lineage: { previousDigest: null } } });
91
+ const receipt = {
92
+ schema: 'routing-capabilities/v1',
93
+ runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha },
94
+ recordedAt: new Date(Date.now() - 60_000).toISOString(), expiresAt: new Date(Date.now() + 6 * 3600_000).toISOString(),
95
+ capabilities: { artifactDelivery: 'supported', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'supported', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' },
96
+ availableSelections: [sel, full], runtimeDefaultSelection: full,
97
+ evidence: ['modelOverride', 'effortOverride', 'observedModelEvidence', 'nativeSubagents', 'artifactDelivery', 'interactiveExecution', 'unattendedController'].map(capability => ({ capability, kind: 'native-control', receiptDigest: 'b'.repeat(64) })),
98
+ approval: { approvalId: 'r1', snapshotDigest: 'c'.repeat(64) },
99
+ };
100
+ const readCapabilities = () => ({ state: 'current', receipt, digest: (0, capabilities_1.capabilityReceiptDigest)(receipt) });
101
+ const program = new commander_1.Command();
102
+ program.exitOverride();
103
+ program.configureOutput({ writeErr: () => undefined });
104
+ (0, plan_1.registerPlanCommand)(program, { validatePlanFile: () => v2Valid, readEffectivePolicy: readEffectivePolicy, readCapabilities: readCapabilities });
105
+ await program.parseAsync(['node', 'awm', 'plan', 'resolve', 'plans/r4.md', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', sha, '--role', 'implementer', '--slice', sliceId, '--cwd', repositoryRoot, '--json']);
106
+ const emitted = JSON.parse(String(stdoutWrite.mock.calls[stdoutWrite.mock.calls.length - 1][0]));
107
+ expect(emitted.state).toBe('resolved');
108
+ expect(emitted.envelope).toBeDefined();
109
+ // The CLI must be the sole author: the envelope has to satisfy the same
110
+ // predicate routing-reserve applies, with nothing filled in by the caller.
111
+ expect((0, types_1.isRoutingEnvelope)(emitted.envelope)).toBe(true);
112
+ expect(emitted.envelope).toMatchObject({ schema: 'routing-envelope/v1', role: 'implementer', sliceId, requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: sel });
113
+ expect(emitted.envelope.planDigest).toBe(v2Valid.planDigest);
114
+ expect(emitted.envelope.executionDigest).toBe(v2Valid.executionDigest);
115
+ // The point of the whole thing: that envelope reserves a first attempt,
116
+ // which is what creates the lineage the escalation path later needs.
117
+ const reserved = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'o1', lineageId: 'l1', envelope: emitted.envelope, fingerprint: 'e'.repeat(64) }, '2026-09-19T00:00:00.000Z');
118
+ expect(reserved.attemptId).toEqual(expect.any(String));
119
+ expect(reserved.state.implementationLineages).toEqual([expect.objectContaining({ id: 'l1', sliceId, initialProfile: 'mechanical' })]);
120
+ });
63
121
  it('emits deterministic human output for a valid compact plan', async () => {
64
122
  await commandFor(valid).parseAsync(['node', 'awm', 'plan', 'validate', 'plans/r4.md']);
65
123
  expect(String(stdoutWrite.mock.calls[0][0])).toBe('Plan validation: valid "plans/r4.md" (compact-slices/v1; 1 slices; 5 requirements; complete ownership)\n');
@@ -390,6 +390,59 @@ describe('supervisor loop', () => {
390
390
  await expect(new supervisor_1.Supervisor(repo, 'main', cfg, spy, undefined, admitted).tick()).rejects.toThrow(/rama|branch/i);
391
391
  expect(calls).toBe(0);
392
392
  });
393
+ // The sensor deferral at the top of tick() covers a first inconclusive read
394
+ // while a controller settles, and returns 'continue' so a transient does not
395
+ // custody a healthy cycle. It returns BEFORE consumePendingRequests, and
396
+ // `sensors: 'not-certified'` is not always transient: it is the steady state
397
+ // of any registry whose sensors are all disabled, which the baseline registry
398
+ // documents about itself. Unbounded, the supervisor ticked forever applying
399
+ // nothing and writing nothing — no event, no state change, no exit. Observed
400
+ // against published 9.10.0: a routing-reserve and an unrelated
401
+ // controller-heartbeat both sat unconsumed for ~36 ticks with appliedRequests
402
+ // empty and the cycle still IN_PROGRESS.
403
+ test('an inconclusive sensor verdict defers dispatch a bounded number of ticks, then says so', async () => {
404
+ const repo = setupRepo();
405
+ initUnattendedFixture(repo);
406
+ (0, generations_1.beginGeneration)(repo, 'main');
407
+ const token = (0, generations_1.activeGeneration)((0, store_1.readJournal)(repo, 'main').state).token;
408
+ (0, request_1.requestJob)(repo, 'main', token, ['node', '-e', 'process.exit(0)'], [], '.');
409
+ // Exactly what `awm plan admit --require-current --verify-sensors` returns
410
+ // for that registry: valid plan, current contracts, sensors not certified.
411
+ // Every field matches the deferral condition.
412
+ const notCertified = async () => ({
413
+ state: 'blocked', planState: 'valid', executionMode: 'desatendido',
414
+ journal: 'not-required', currentness: 'current', sensors: 'not-certified',
415
+ diagnostics: [{ code: 'ADMISSION_SENSORS_BLOCKED', message: 'sensors not certified' }],
416
+ });
417
+ let spawned = 0;
418
+ const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawned += 1; }, undefined, notCertified);
419
+ for (let i = 0; i < supervisor_1.MAX_SENSOR_DEFERRALS; i += 1) {
420
+ expect(await sup.tick()).toBe('continue');
421
+ expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
422
+ }
423
+ // The verdict never settled, so the supervisor must stop pretending it might.
424
+ expect(await sup.tick()).toBe('custody');
425
+ const state = (0, store_1.readJournal)(repo, 'main').state;
426
+ expect(state.cycle.status).toBe('BLOCKED');
427
+ expect(state.cycle.blockedReason).toMatch(/sensor/i);
428
+ // Entered once, not once per tick. enterCustody is idempotent on
429
+ // (status, reason), so the reason must not carry the running counter —
430
+ // otherwise every tick rewrites the journal and appends another
431
+ // custody-blocked event, unbounded. Observed against published 9.10.1
432
+ // before this was fixed: "tras 16 ticks", "tras 17 ticks", "tras 18
433
+ // ticks", one every 5s.
434
+ const eventsFile = path_1.default.join(repo, '.awm', 'journal', 'main', 'events.jsonl');
435
+ const custodyEvents = () => fs_1.default.readFileSync(eventsFile, 'utf8').split('\n').filter(line => line.includes('custody-blocked')).length;
436
+ const afterFirst = custodyEvents();
437
+ expect(afterFirst).toBe(1);
438
+ const revisionAfterFirst = state.revision;
439
+ for (let i = 0; i < 3; i += 1)
440
+ expect(await sup.tick()).toBe('custody');
441
+ expect(custodyEvents()).toBe(afterFirst);
442
+ expect((0, store_1.readJournal)(repo, 'main').state.revision).toBe(revisionAfterFirst);
443
+ // Deferring dispatch was right throughout, and stays right.
444
+ expect(spawned).toBe(0);
445
+ });
393
446
  test('una admisión compacta bloqueada no lanza controlador ni wrappers', async () => {
394
447
  initUnattendedFixture(repo);
395
448
  const state = (0, store_1.readJournal)(repo, 'main').state;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.10.0",
3
+ "version": "9.10.2",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file