agentic-workflow-manager 9.15.1 → 9.16.0

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.
@@ -25,7 +25,10 @@ exports.PROVIDER_EXECUTION_CAPABILITIES = {
25
25
  opencode: { ...UNKNOWN },
26
26
  'claude-code': { ...UNKNOWN, interactiveExecution: 'supported', unattendedController: 'supported', durableResume: 'supported' },
27
27
  codex: { ...UNKNOWN, interactiveExecution: 'supported', unattendedController: 'supported', durableResume: 'supported' },
28
- cursor: { ...UNKNOWN },
28
+ // Plan C (native session only): interactive + journal-less unattended v1.
29
+ // durableResume stays unverified — Camino B / awm watch is suspended (D-023)
30
+ // and Cursor is not in WATCH_PROVIDERS. Evidence: docs/testing/cursor-execution-evidence.md
31
+ cursor: { ...UNKNOWN, interactiveExecution: 'supported', unattendedController: 'supported' },
29
32
  copilot: { ...UNKNOWN },
30
33
  };
31
34
  /** #168: the controller can only act if it was launched with an autonomy
@@ -163,6 +163,15 @@ function providers() {
163
163
  cursor: {
164
164
  label: 'Cursor',
165
165
  configHome: configHomeFor('cursor'),
166
+ // Date-based CLI line (YYYY.MM.DD-<hash>). compareSemver orders the
167
+ // date triple as [YYYY, M, D]. Floor from owner-machine capture
168
+ // 2026.09.26-dd393fe (docs/testing/cursor-execution-evidence.md).
169
+ minimumVersion: '2026.09.26',
170
+ versionCommand: {
171
+ command: 'agent',
172
+ args: ['--version'],
173
+ versionPattern: /^(\d{4}\.\d{2}\.\d{2})-[0-9a-f]+$/,
174
+ },
166
175
  // Skills join the Codex/OpenCode shared physical dirs (~/.agents/skills +
167
176
  // .agents/skills, renderer link). cursor-mdc remains registered for S3
168
177
  // migration reads of historical ~/.cursor/rules installs.
@@ -434,6 +434,10 @@ describe('runInit', () => {
434
434
  agent: 'cursor',
435
435
  yes: true,
436
436
  machineOnly: true,
437
+ // Plan C adds a Cursor version gate; CI has no `agent` binary.
438
+ // Stub the same way Codex init tests do — the gate itself is
439
+ // covered in provider-version.test.ts.
440
+ assertProviderSupported: () => ({ provider: 'cursor', version: '2026.09.26' }),
437
441
  // seedBaselineRegistry() writes registries.json pointing at 'baseline'
438
442
  // (our manually-seeded content root above) since none exists yet — no
439
443
  // real network clone happens because that content root already exists
@@ -27,7 +27,7 @@ describe('admitPlan', () => {
27
27
  });
28
28
  it.each([
29
29
  ['journal', { provider: 'codex', executionMode: 'desatendido' }],
30
- ['provider capability', { provider: 'cursor', executionMode: 'interactivo' }],
30
+ ['provider capability', { provider: 'copilot', executionMode: 'interactivo' }],
31
31
  ])('does not invoke deferred v2 routing facts when %s gate blocks', async (_name, fields) => {
32
32
  const reader = jest.fn(() => { throw new Error('routing reader must not run'); });
33
33
  const report = await (0, admission_1.admitPlan)({ plan: v2(), cwd: process.cwd(), enabledAgents: [fields.provider], routingReader: reader, ...fields });
@@ -139,10 +139,32 @@ describe('admitPlan', () => {
139
139
  expect(report.currentness).toBe('not-checked');
140
140
  });
141
141
  it('does not let an unverified execution capability admit interactive work', async () => {
142
- const report = await (0, admission_1.admitPlan)({ plan: valid, provider: 'cursor', cwd: process.cwd(), enabledAgents: ['cursor'] });
143
- expect(report).toMatchObject({ state: 'blocked', executionMode: 'interactivo', provider: 'cursor', journal: 'not-required' });
142
+ const report = await (0, admission_1.admitPlan)({ plan: valid, provider: 'copilot', cwd: process.cwd(), enabledAgents: ['copilot'] });
143
+ expect(report).toMatchObject({ state: 'blocked', executionMode: 'interactivo', provider: 'copilot', journal: 'not-required' });
144
144
  expect(report.diagnostics[0].code).toBe('ADMISSION_CAPABILITY_UNVERIFIED');
145
145
  });
146
+ it('admits Cursor interactive work once Plan C marks interactiveExecution supported', async () => {
147
+ const report = await (0, admission_1.admitPlan)({ plan: valid, provider: 'cursor', cwd: process.cwd(), enabledAgents: ['cursor'] });
148
+ expect(report).toMatchObject({ state: 'admitted', executionMode: 'interactivo', provider: 'cursor', journal: 'not-required' });
149
+ expect(report.capabilityResolution).toMatchObject({ outcome: 'native', capabilities: { interactiveExecution: 'supported' } });
150
+ });
151
+ it('admits Cursor journal-less unattended v1 with an explicit autonomy posture', async () => {
152
+ const unattended = { ...valid, manifest: { ...valid.manifest, executionMode: 'desatendido' } };
153
+ const report = await (0, admission_1.admitPlan)({
154
+ plan: unattended, provider: 'cursor', cwd: process.cwd(), enabledAgents: ['cursor'],
155
+ planPath: 'docs/plan.md', controllerAutonomy: 'approval-free',
156
+ });
157
+ expect(report).toMatchObject({ state: 'admitted', executionMode: 'desatendido', provider: 'cursor', journal: 'not-required' });
158
+ expect(report.capabilityResolution).toMatchObject({ outcome: 'native', capabilities: { unattendedController: 'supported' } });
159
+ });
160
+ it('still requires the autonomy posture for Cursor unattended work', async () => {
161
+ const unattended = { ...valid, manifest: { ...valid.manifest, executionMode: 'desatendido' } };
162
+ const report = await (0, admission_1.admitPlan)({
163
+ plan: unattended, provider: 'cursor', cwd: process.cwd(), enabledAgents: ['cursor'], planPath: 'docs/plan.md',
164
+ });
165
+ expect(report.state).toBe('blocked');
166
+ expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_CONTROLLER_AUTONOMY_REQUIRED' });
167
+ });
146
168
  // The durable controller (journal + awm watch + jobs) is opt-in again for
147
169
  // native v1 dispatch, as it was before 9.8.0: a v1 unattended plan with no
148
170
  // journal on its branch runs as one native provider session. Creating a
@@ -266,7 +288,7 @@ describe('admitPlan', () => {
266
288
  expect(report.state).toBe('admitted');
267
289
  });
268
290
  it('keeps the original diagnostic for a provider that never had the capability', async () => {
269
- const report = await (0, admission_1.admitPlan)({ plan: unattended(), provider: 'cursor', cwd: process.cwd(), enabledAgents: ['cursor'], journalState: journal(), planPath: 'docs/plan.md', controllerAutonomy: 'approval-free' });
291
+ const report = await (0, admission_1.admitPlan)({ plan: unattended(), provider: 'copilot', cwd: process.cwd(), enabledAgents: ['copilot'], journalState: journal(), planPath: 'docs/plan.md', controllerAutonomy: 'approval-free' });
270
292
  expect(report.state).toBe('blocked');
271
293
  expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_CAPABILITY_UNVERIFIED' });
272
294
  });
@@ -58,8 +58,15 @@ describe('unattendedController stops being asserted for free (#168)', () => {
58
58
  expect((0, admission_1.unattendedCapabilities)('claude-code', 'approval-free').unattendedController).toBe('supported');
59
59
  });
60
60
  test('never promotes a provider that never had the capability', () => {
61
- expect(admission_1.PROVIDER_EXECUTION_CAPABILITIES.cursor.unattendedController).toBe('unverified');
62
- expect((0, admission_1.unattendedCapabilities)('cursor', 'approval-free').unattendedController).toBe('unverified');
61
+ expect(admission_1.PROVIDER_EXECUTION_CAPABILITIES.copilot.unattendedController).toBe('unverified');
62
+ expect((0, admission_1.unattendedCapabilities)('copilot', 'approval-free').unattendedController).toBe('unverified');
63
+ });
64
+ test('Cursor gains unattendedController only with a declared posture (Plan C)', () => {
65
+ expect(admission_1.PROVIDER_EXECUTION_CAPABILITIES.cursor.unattendedController).toBe('supported');
66
+ expect((0, admission_1.unattendedCapabilities)('cursor', undefined).unattendedController).toBe('unverified');
67
+ expect((0, admission_1.unattendedCapabilities)('cursor', 'approval-free').unattendedController).toBe('supported');
68
+ // Watch/custody resume is still unverified and suspended (D-023).
69
+ expect(admission_1.PROVIDER_EXECUTION_CAPABILITIES.cursor.durableResume).toBe('unverified');
63
70
  });
64
71
  });
65
72
  describe('awm watch surfaces the posture on the compiled binary (#168)', () => {
@@ -75,6 +75,26 @@ describe('assertProviderSupported', () => {
75
75
  });
76
76
  expect(exec).not.toHaveBeenCalled();
77
77
  });
78
+ it('accepts the Cursor date-based agent line (Plan C)', () => {
79
+ const exec = jest.fn(() => Buffer.from('2026.09.26-dd393fe\n'));
80
+ expect((0, provider_version_1.assertProviderSupported)('cursor', exec)).toEqual({
81
+ provider: 'cursor',
82
+ version: '2026.09.26',
83
+ });
84
+ expect(exec).toHaveBeenCalledWith('agent', ['--version'], expect.any(Object));
85
+ });
86
+ it.each([
87
+ [Buffer.from('2026.09.25-abc1234\n'), 'requires Cursor >= 2026.09.26'],
88
+ [Buffer.from('2026.09.26\n'), 'could not parse Cursor version'],
89
+ [Buffer.from('v2026.09.26-dd393fe\n'), 'could not parse Cursor version'],
90
+ ])('rejects unsupported Cursor output without mutation', (output, message) => {
91
+ expect(() => (0, provider_version_1.assertProviderSupported)('cursor', () => output)).toThrow(message);
92
+ });
93
+ it('reports a missing Cursor agent binary distinctly', () => {
94
+ const missing = Object.assign(new Error('spawnSync agent ENOENT'), { code: 'ENOENT' });
95
+ expect(() => (0, provider_version_1.assertProviderSupported)('cursor', () => { throw missing; }))
96
+ .toThrow('Cursor is not installed or not available on PATH');
97
+ });
78
98
  it('reports a missing Codex binary distinctly', () => {
79
99
  const missing = Object.assign(new Error('spawnSync codex ENOENT'), { code: 'ENOENT' });
80
100
  expect(() => (0, provider_version_1.assertProviderSupported)('codex', () => { throw missing; }))
@@ -210,6 +210,14 @@ describe('Providers Routing', () => {
210
210
  });
211
211
  expect((0, providers_1.providerFor)('copilot').hooks).toBeUndefined();
212
212
  });
213
+ it('declares Cursor agent version gate (Plan C R26)', () => {
214
+ expect((0, providers_1.providerFor)('cursor').minimumVersion).toBe('2026.09.26');
215
+ expect((0, providers_1.providerFor)('cursor').versionCommand).toEqual({
216
+ command: 'agent',
217
+ args: ['--version'],
218
+ versionPattern: /^(\d{4}\.\d{2}\.\d{2})-[0-9a-f]+$/,
219
+ });
220
+ });
213
221
  it('assigns Copilot instructions renderer; Cursor skills use link (cursor-mdc kept for migration)', () => {
214
222
  expect((0, providers_1.providerFor)('cursor').skill.renderer).toBe('link');
215
223
  expect((0, providers_1.providerFor)('copilot').skill.renderer).toBe('copilot-instructions');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.15.1",
3
+ "version": "9.16.0",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file