agentic-orchestrator 0.2.1 → 0.2.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/apps/control-plane/src/core/path-layout.ts +23 -5
- package/apps/control-plane/test/path-layout.spec.ts +11 -0
- package/dist/apps/control-plane/core/path-layout.js +20 -1
- package/dist/apps/control-plane/core/path-layout.js.map +1 -1
- package/package.json +1 -1
- package/packages/web-dashboard/package.json +1 -1
- package/packages/web-dashboard/src/lib/orchestrator-tools.ts +15 -8
- package/packages/web-dashboard/test/api/launch/submit.route.spec.ts +30 -0
- package/packages/web-dashboard/test/lib/launch-contracts.spec.ts +65 -0
|
@@ -1,7 +1,28 @@
|
|
|
1
|
+
import fsSync from 'node:fs';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { ensureDir, pathExists } from './fs.js';
|
|
4
6
|
|
|
7
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
function resolveBundledOrchestratorRoot(startDir: string): string | null {
|
|
10
|
+
let current = path.resolve(startDir);
|
|
11
|
+
while (true) {
|
|
12
|
+
const candidate = path.join(current, 'agentic', 'orchestrator', 'schemas');
|
|
13
|
+
if (fsSync.existsSync(candidate)) {
|
|
14
|
+
return path.join(current, 'agentic', 'orchestrator');
|
|
15
|
+
}
|
|
16
|
+
const parent = path.dirname(current);
|
|
17
|
+
if (parent === current) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
current = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BUNDLED_ORCHESTRATOR_ROOT = resolveBundledOrchestratorRoot(MODULE_DIR);
|
|
25
|
+
|
|
5
26
|
export class AopPathLayout {
|
|
6
27
|
readonly repoRoot: string;
|
|
7
28
|
|
|
@@ -54,11 +75,8 @@ export class AopPathLayout {
|
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
organizerOrderingSchemaPath(): string {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'schemas',
|
|
60
|
-
'organizer-ordering-artifact.schema.json',
|
|
61
|
-
);
|
|
78
|
+
const root = BUNDLED_ORCHESTRATOR_ROOT ?? this.legacyOrchestratorRoot;
|
|
79
|
+
return path.join(root, 'schemas', 'organizer-ordering-artifact.schema.json');
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
get legacyFeaturesRoot(): string {
|
|
@@ -13,6 +13,17 @@ async function writeMarker(filePath: string, content = 'marker'): Promise<void>
|
|
|
13
13
|
await fs.writeFile(filePath, content, 'utf8');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
describe('AopPathLayout', () => {
|
|
17
|
+
it('resolves organizerOrderingSchemaPath from the bundled package tree', () => {
|
|
18
|
+
const layout = new AopPathLayout('/some/external/project');
|
|
19
|
+
const schemaPath = layout.organizerOrderingSchemaPath();
|
|
20
|
+
|
|
21
|
+
expect(schemaPath).toContain('organizer-ordering-artifact.schema.json');
|
|
22
|
+
// Must NOT resolve from the arbitrary repoRoot — it must find the bundled schema
|
|
23
|
+
expect(schemaPath).not.toContain('/some/external/project/');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
16
27
|
describe('ensureAopRuntimeLayout', () => {
|
|
17
28
|
it('migrates legacy features when .aop is missing', async () => {
|
|
18
29
|
const repoRoot = await makeTempDir();
|
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
import fsSync from 'node:fs';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { ensureDir, pathExists } from './fs.js';
|
|
6
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
function resolveBundledOrchestratorRoot(startDir) {
|
|
8
|
+
let current = path.resolve(startDir);
|
|
9
|
+
while (true) {
|
|
10
|
+
const candidate = path.join(current, 'agentic', 'orchestrator', 'schemas');
|
|
11
|
+
if (fsSync.existsSync(candidate)) {
|
|
12
|
+
return path.join(current, 'agentic', 'orchestrator');
|
|
13
|
+
}
|
|
14
|
+
const parent = path.dirname(current);
|
|
15
|
+
if (parent === current) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
current = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const BUNDLED_ORCHESTRATOR_ROOT = resolveBundledOrchestratorRoot(MODULE_DIR);
|
|
4
22
|
export class AopPathLayout {
|
|
5
23
|
repoRoot;
|
|
6
24
|
constructor(repoRoot) {
|
|
@@ -40,7 +58,8 @@ export class AopPathLayout {
|
|
|
40
58
|
return path.join(this.instanceRuntimeRoot(instanceId), 'organizer-ordering.json');
|
|
41
59
|
}
|
|
42
60
|
organizerOrderingSchemaPath() {
|
|
43
|
-
|
|
61
|
+
const root = BUNDLED_ORCHESTRATOR_ROOT ?? this.legacyOrchestratorRoot;
|
|
62
|
+
return path.join(root, 'schemas', 'organizer-ordering-artifact.schema.json');
|
|
44
63
|
}
|
|
45
64
|
get legacyFeaturesRoot() {
|
|
46
65
|
return path.join(this.repoRoot, 'agentic', 'features');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-layout.js","sourceRoot":"","sources":["../../../../apps/control-plane/src/core/path-layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,OAAO,aAAa;IACf,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,IAAI,gBAAgB;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,sBAAsB;QACxB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,mBAAmB;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;IAC7D,CAAC;IAED,mBAAmB,CAAC,UAAkB;QACpC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IACrD,CAAC;IAED,gBAAgB,CAAC,UAAkB;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC3E,CAAC;IAED,gBAAgB,CAAC,UAAkB;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAC5E,CAAC;IAED,oBAAoB,CAAC,UAAkB;QACrC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,wBAAwB,CAAC,CAAC;IACnF,CAAC;IAED,qBAAqB,CAAC,UAAkB;QACtC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,yBAAyB,CAAC,CAAC;IACpF,CAAC;IAED,2BAA2B;QACzB,
|
|
1
|
+
{"version":3,"file":"path-layout.js","sourceRoot":"","sources":["../../../../apps/control-plane/src/core/path-layout.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
import { getAopRoot } from '@/lib/aop-client.js';
|
|
6
6
|
import {
|
|
7
7
|
submitDashboardRuntimeCommand,
|
|
@@ -348,16 +348,23 @@ function resolveControlPlaneModulePath(): string {
|
|
|
348
348
|
return fromAopRoot;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
// Strategy 3:
|
|
351
|
+
// Strategy 3: Walk up from this file's compiled location (handles .next chunks
|
|
352
|
+
// within the installed package tree, e.g. packages/web-dashboard/.next/dev/...)
|
|
352
353
|
try {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
354
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
355
|
+
for (let i = 0; i < 10; i++) {
|
|
356
|
+
const candidate = path.join(dir, distRelative);
|
|
357
|
+
if (existsSync(candidate)) {
|
|
358
|
+
return candidate;
|
|
359
|
+
}
|
|
360
|
+
const parent = path.dirname(dir);
|
|
361
|
+
if (parent === dir) {
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
dir = parent;
|
|
358
365
|
}
|
|
359
366
|
} catch {
|
|
360
|
-
//
|
|
367
|
+
// import.meta.url may not resolve to a file path in all bundler contexts
|
|
361
368
|
}
|
|
362
369
|
|
|
363
370
|
// Fallback: return the AOP_ROOT-based path (will fail at import time with a clear error)
|
|
@@ -261,6 +261,36 @@ describe('POST /api/launch/submit', () => {
|
|
|
261
261
|
).resolves.toContain('Ship it.');
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
+
it('accepts uploaded drafts where name is stripped but source_path retains .md', async () => {
|
|
265
|
+
const response = await POST(
|
|
266
|
+
new Request('http://localhost/api/launch/submit', {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
drafts: [
|
|
270
|
+
{
|
|
271
|
+
draft_id: 'upload-stripped',
|
|
272
|
+
draft_order: 0,
|
|
273
|
+
source_type: 'upload',
|
|
274
|
+
name: 'my-feature',
|
|
275
|
+
source_path: 'my-feature.md',
|
|
276
|
+
markdown: '# My Feature\n\nDescription.',
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
}),
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
const body = (await response.json()) as {
|
|
283
|
+
ok: boolean;
|
|
284
|
+
data?: { outcomes: Array<{ draft_id: string; status: string; errors: string[] }> };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
expect(body.ok).toBe(true);
|
|
288
|
+
expect(body.data?.outcomes[0]).toMatchObject({
|
|
289
|
+
draft_id: 'upload-stripped',
|
|
290
|
+
status: 'submitted',
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
264
294
|
it('rejects duplicate slugs before writing specs or submitting enrollment requests', async () => {
|
|
265
295
|
prepareLaunchDraftSpecsMock.mockResolvedValue([
|
|
266
296
|
{
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { validateLaunchDraftInput } from '@/lib/launch-contracts.js';
|
|
3
|
+
import type { LaunchDraftInput } from '@/lib/launch-contracts.js';
|
|
4
|
+
|
|
5
|
+
function baseDraft(overrides: Partial<LaunchDraftInput> = {}): LaunchDraftInput {
|
|
6
|
+
return {
|
|
7
|
+
draft_id: 'draft-1',
|
|
8
|
+
draft_order: 0,
|
|
9
|
+
source_type: 'paste',
|
|
10
|
+
name: 'Feature Draft',
|
|
11
|
+
markdown: '# Feature',
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('validateLaunchDraftInput', () => {
|
|
17
|
+
it('returns no errors for a valid paste draft', () => {
|
|
18
|
+
expect(validateLaunchDraftInput(baseDraft())).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns no errors for a valid upload draft with .md extension', () => {
|
|
22
|
+
expect(validateLaunchDraftInput(baseDraft({ source_type: 'upload', name: 'spec.md' }))).toEqual(
|
|
23
|
+
[],
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('rejects uploaded drafts with unsupported extensions', () => {
|
|
28
|
+
const errors = validateLaunchDraftInput(
|
|
29
|
+
baseDraft({ source_type: 'upload', name: 'notes.txt' }),
|
|
30
|
+
);
|
|
31
|
+
expect(errors).toContain('Only .md draft uploads are supported.');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('accepts uploaded drafts when name is stripped but source_path retains .md', () => {
|
|
35
|
+
const errors = validateLaunchDraftInput(
|
|
36
|
+
baseDraft({ source_type: 'upload', name: 'my-feature', source_path: 'my-feature.md' }),
|
|
37
|
+
);
|
|
38
|
+
expect(errors).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('rejects uploaded drafts when both name and source_path lack .md extension', () => {
|
|
42
|
+
const errors = validateLaunchDraftInput(
|
|
43
|
+
baseDraft({ source_type: 'upload', name: 'my-feature', source_path: 'my-feature.txt' }),
|
|
44
|
+
);
|
|
45
|
+
expect(errors).toContain('Only .md draft uploads are supported.');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('rejects drafts with empty name', () => {
|
|
49
|
+
expect(validateLaunchDraftInput(baseDraft({ name: ' ' }))).toContain(
|
|
50
|
+
'Draft name is required.',
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('rejects drafts with empty markdown', () => {
|
|
55
|
+
expect(validateLaunchDraftInput(baseDraft({ markdown: '' }))).toContain(
|
|
56
|
+
'Draft markdown is required.',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects drafts with empty draft_id', () => {
|
|
61
|
+
expect(validateLaunchDraftInput(baseDraft({ draft_id: '' }))).toContain(
|
|
62
|
+
'draft_id is required.',
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|