Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ jobs:
timeout-minutes: 20
run: npm ci --no-audit --no-fund

- name: Verify package
- name: Install exact integrated SDK
uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@c57912d9ce2e05bfb09209b03aa7c3a1d9489585
with:
github-token: ${{ github.token }}

- name: Verify package with exact SDK
run: npm run verify:direct

- name: Seal protected staging candidate
Expand Down
5 changes: 3 additions & 2 deletions src/cli/commands/development-support/overlays.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spawn } from 'node:child_process';
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, rmSync, symlinkSync } from 'node:fs';
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, rmSync, symlinkSync, unlinkSync } from 'node:fs';
import { dirname, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { DevelopmentRuntime, DevelopmentTarget } from '@treeseed/sdk/development';
Expand Down Expand Up @@ -93,7 +93,8 @@ export function installPackageOverlay(state: OverlaySessionState, record: { sess
continue;
}
if (repair) {
rmSync(link, { recursive: true, force: true });
// This path was verified as our recorded symbolic link, not a directory.
unlinkSync(link);
symlinkSync(relativeOverlayTarget(link, overlayRoot), link, 'dir');
continue;
}
Expand Down
16 changes: 16 additions & 0 deletions src/cli/commands/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolve } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { controlPlaneOperation, encodeConfirmationState, parseCommunicationAddresses, validateWorkdayIntentSelection, normalizeWorkdayAgentSelection, type CommandInputBinding } from '@treeseed/sdk/operator-contracts';
import { ControlPlaneClientError, resolveControlPlaneServer } from '@treeseed/sdk/control-plane-client';
import { workdayAllocationOverridesSchema } from '@treeseed/sdk/agent-capacity';
import type { CommandContext, ParsedInvocation } from '../types.js';
import { launchApplication } from '../application/launch.js';
import { runInteractiveChat } from '../communication/interactive-chat.js';
Expand Down Expand Up @@ -34,7 +35,17 @@ function sourceValue(binding: CommandInputBinding, invocation: ParsedInvocation,
function transform(value: unknown, binding: CommandInputBinding) {
if (value === undefined || value === null) return undefined;
if (binding.transform === 'csv') return (Array.isArray(value) ? value : [value]).flatMap(item => String(item).split(',').map(part => part.trim()));
if (binding.transform === 'json') {
try { return JSON.parse(String(value)); } catch { throw Object.assign(new Error(`Invalid JSON for --${binding.name}.`),
{ category: 'invalid_input', code: 'command_json_invalid' }); }
}
if (value === '') return undefined;
if (binding.transform === 'number') {
const parsed = Number(value);
if (!Number.isFinite(parsed)) throw Object.assign(new Error(`${binding.name} must be a finite number.`),
{ category: 'invalid_input', code: 'command_number_invalid' });
return parsed;
}
if (binding.transform === 'integer') {
const parsed = Number(value);
if (!Number.isInteger(parsed)) throw new Error(`${binding.name} must be an integer.`);
Expand Down Expand Up @@ -71,6 +82,11 @@ async function operationInput(invocation: ParsedInvocation, context: CommandCont
if (value !== undefined) setOperationInputField(input[binding.target], binding.field, value);
}
const operation = controlPlaneOperation(invocation.command.execution.operationId);
if (operation.descriptor.operationId === 'workdays.plan' && input.body.allocation !== undefined) {
const parsed = workdayAllocationOverridesSchema.safeParse(input.body.allocation);
if (!parsed.success) throw Object.assign(new Error(parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join(' ')),
{ category: 'invalid_input', code: 'workday_allocation_invalid' });
}
if (operation.descriptor.operationId === 'workdays.plan' && input.body.agentSelection !== undefined) {
const diagnostics = validateWorkdayIntentSelection(input.body.agentSelection);
if (diagnostics.length) throw Object.assign(new Error(diagnostics.map(item => `${item.path}: ${item.message}`).join(' ')), { category: 'invalid_input', code: 'workday_agent_selection_invalid' });
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/command-boundary/workdays/selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ import { getOperationInputField, setOperationInputField } from '../../../../src/

const base = ['workdays', 'plan', '--team', '11111111-1111-4111-8111-111111111111', '--profile', 'documentation', '--projects', 'sdk', '--start', '2030-01-01T00:00:00Z', '--duration', '600', '--json'];

test('high-level allocation options use one nested policy input', async () => {
let body: any;
assert.equal(await runCommandLine([...base, '--planning-percent', '20', '--allocation-weight', '2',
'--planning-turn-maximum-seconds', '180', '--project-percentages', '{"sdk":60,"api":40}',
'--agent-class-percentages', '{"sdk":{"engineer":100}}'], {
interactiveUi: false, write() {}, operationInvoke: async (_id, input) => { body = input.body; return { data: {} }; },
}), 0);
assert.deepEqual(body.allocation, { planningPercent: 20, allocationWeight: 2, planningTurnMaximumSeconds: 180,
projectPercentages: { sdk: 60, api: 40 }, agentClassPercentages: { sdk: { engineer: 100 } } });
});

for (const options of [['--project-percentages', 'invalid'], ['--allocation-weight', '0'], ['--planning-percent', '101']]) {
test(`invalid allocation ${JSON.stringify(options)} never invokes the API`, async () => {
let calls = 0;
assert.equal(await runCommandLine([...base, ...options], { interactiveUi: false, write() {},
operationInvoke: async () => { calls++; } }), 1);
assert.equal(calls, 0);
});
}

test('repeated and CSV selectors become a normalized intersecting nested intent', async () => {
const calls: Array<{ operationId: string; input: any }> = [];
const exit = await runCommandLine([...base, '--agent', 'reviewer,architect', '--agent', 'reviewer', '--activity', 'reviewing', '--class', 'engineering'], {
Expand Down
Loading