Skip to content
Open
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
359 changes: 359 additions & 0 deletions packages/runtime/src/cloud-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import type { PersonaSpec } from '@agentworkforce/persona-kit';
import { createCloudRuntimeDefaults } from './cloud-defaults.js';

const persona: PersonaSpec = {
id: 'demo',
intent: 'documentation',
tags: ['documentation'],
description: 'test persona',
skills: [],
harness: 'claude',
model: 'anthropic/claude-3-5-sonnet',
systemPrompt: 'be helpful',
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 },
cloud: true
};

const deployment = {
id: 'deployment_123',
triggerKind: 'inbox' as const,
parentDeploymentId: null
};

function defaultsFor(overrides: {
workspaceId?: string;
agentId?: string;
env?: NodeJS.ProcessEnv;
} = {}) {
return createCloudRuntimeDefaults({
persona,
agent: {
id: overrides.agentId ?? 'agent_parent',
deployedName: 'demo',
spawnedByAgentId: null
},
deployment,
workspaceId: overrides.workspaceId ?? 'ws_test',
log: () => {
/* keep test output quiet */
},
env: {
WORKFORCE_SANDBOX_ROOT: '/tmp',
...overrides.env
}
});
}

test('createCloudRuntimeDefaults omits team without cloud env or path ids', () => {
assert.equal(defaultsFor().team, undefined);
assert.equal(defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
},
workspaceId: ''
}).team, undefined);
assert.equal(defaultsFor({
agentId: '',
env: {
WORKFORCE_WORKSPACE_TOKEN: 'token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
}).team, undefined);
});

test('createCloudRuntimeDefaults attaches team with cloud env and path ids', () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});

assert.equal(typeof defaults.team?.spawn, 'function');
assert.equal(typeof defaults.team?.attach, 'function');
});

test('ctx.team.spawn posts documented body and returns seeded handle', async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
await withFetch(async (input, init) => {
calls.push({ url: String(input), init });
return jsonResponse({
teamId: 'team_123',
channel: 'team-team_123',
sharedMountRoot: '/teams/team_123',
status: 'starting',
members: []
}, { status: 201 });
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test/'
}
});
const handle = await defaults.team!.spawn({
task: 'Refactor auth',
teamPrompt: 'Coordinate through the team board',
members: [{ name: 'lead', persona: 'relay-orchestrator', role: 'orchestrator' }],
sharedMount: 'issue-421',
ttlSeconds: 3600,
maxMembers: 8
});

assert.equal(handle.teamId, 'team_123');
assert.equal(handle.channel, 'team-team_123');
assert.equal(handle.sharedMountRoot, '/teams/team_123');
});

assert.equal(calls.length, 1);
assert.equal(calls[0].url, 'https://cloud.example.test/api/v1/workspaces/ws_test/agents/agent_parent/team');
assert.equal(calls[0].init?.method, 'POST');
assert.deepEqual(calls[0].init?.headers, {
accept: 'application/json',
'content-type': 'application/json',
authorization: 'Bearer workspace-token'
});
assert.deepEqual(JSON.parse(String(calls[0].init?.body)), {
task: 'Refactor auth',
teamPrompt: 'Coordinate through the team board',
members: [{ name: 'lead', persona: 'relay-orchestrator', role: 'orchestrator' }],
sharedMount: 'issue-421',
ttlSeconds: 3600,
maxMembers: 8
});
});

test('ctx.team.spawn throws when cloud response omits teamId', async () => {
await withFetch(async () => jsonResponse({}, { status: 201 }), async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});

await assert.rejects(
() => defaults.team!.spawn({ task: 'Refactor auth', members: [] }),
/ctx\.team\.spawn\(\): cloud response missing teamId/
);
});
});

test('ctx.team.completion polls until succeeded and returns team result', async () => {
const statuses = [
{ teamId: 'team_123', status: 'running', members: [], results: {}, summary: '' },
{
teamId: 'team_123',
status: 'succeeded',
members: [{ name: 'impl', status: 'succeeded' }],
results: { impl: { status: 'succeeded', output: 'done', resultId: 'result_1' } },
summary: 'all done'
}
];
await withFetch(async (input, init) => {
if (String(input).endsWith('/team')) {
return jsonResponse({ teamId: 'team_123', channel: 'team-team_123', sharedMountRoot: '/teams/team_123' }, { status: 201 });
}
assert.equal(init?.method, 'GET');
return jsonResponse(statuses.shift() ?? statuses[0]);
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.spawn({ task: 'Refactor auth', members: [] });

assert.deepEqual(await handle.completion(), {
status: 'succeeded',
members: { impl: { status: 'succeeded', output: 'done', resultId: 'result_1' } },
summary: 'all done'
});
});
});

test('ctx.team.completion maps terminal failure statuses', async () => {
for (const terminal of ['failed', 'timed_out', 'cancelled'] as const) {
await withFetch(async (input) => {
if (String(input).endsWith('/team')) {
return jsonResponse({ teamId: `team_${terminal}` }, { status: 201 });
}
return jsonResponse({
teamId: `team_${terminal}`,
status: terminal,
results: { impl: { status: terminal, output: terminal } },
summary: terminal
});
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.spawn({ task: terminal, members: [] });

assert.deepEqual(await handle.completion(), {
status: terminal,
members: { impl: { status: terminal, output: terminal } },
summary: terminal
});
});
}
});

test('ctx.team.attach derives handle fields and confirms status endpoint', async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
await withFetch(async (input, init) => {
calls.push({ url: String(input), init });
return jsonResponse({ teamId: 'team_abc', status: 'running', members: [], results: {}, summary: '' });
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.attach(' team_abc ');

assert.equal(handle.teamId, 'team_abc');
assert.equal(handle.channel, 'team-team_abc');
assert.equal(handle.sharedMountRoot, '/teams/team_abc');
assert.deepEqual(await handle.status(), {
teamId: 'team_abc',
status: 'running',
members: [],
results: {},
summary: ''
});
});

assert.deepEqual(calls.map((call) => [call.init?.method, call.url]), [
['GET', 'https://cloud.example.test/api/v1/workspaces/ws_test/teams/team_abc'],
['GET', 'https://cloud.example.test/api/v1/workspaces/ws_test/teams/team_abc']
]);
});

test('ctx.team.cancel posts to the cancel endpoint and throws on non-2xx', async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
await withFetch(async (input, init) => {
calls.push({ url: String(input), init });
if (String(input).endsWith('/cancel')) return new Response(null, { status: 204 });
return jsonResponse({ teamId: 'team_cancel', status: 'running' });
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.attach('team_cancel');

await handle.cancel();
});

assert.deepEqual(calls.map((call) => [call.init?.method, call.url]), [
['GET', 'https://cloud.example.test/api/v1/workspaces/ws_test/teams/team_cancel'],
['POST', 'https://cloud.example.test/api/v1/workspaces/ws_test/teams/team_cancel/cancel']
]);

await withFetch(async (input) => {
if (String(input).endsWith('/cancel')) return new Response('nope', { status: 500, statusText: 'Server Error' });
return jsonResponse({ teamId: 'team_cancel', status: 'running' });
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.attach('team_cancel');

await assert.rejects(() => handle.cancel(), /ctx\.team\.attach\("team_cancel"\)\.cancel\(\): 500 Server Error - nope/);
});
});

test('ctx.team.completion retries transient status errors within the budget', async () => {
let statusCalls = 0;
await withFetch(async (input) => {
if (String(input).endsWith('/team')) {
return jsonResponse({ teamId: 'team_retry' }, { status: 201 });
}
statusCalls += 1;
if (statusCalls === 1) return new Response('try again', { status: 503, statusText: 'Unavailable' });
return jsonResponse({ teamId: 'team_retry', status: 'succeeded', results: {}, summary: 'done' });
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.spawn({ task: 'retry', members: [] });

assert.deepEqual(await handle.completion(), {
status: 'succeeded',
members: {},
summary: 'done'
});
assert.equal(statusCalls, 2);
});
});

test('ctx.team.completion throws once the transient retry budget is exhausted', async () => {
let statusCalls = 0;
await withFetch(async (input) => {
if (String(input).endsWith('/team')) {
return jsonResponse({ teamId: 'team_exhaust' }, { status: 201 });
}
statusCalls += 1;
return new Response('try again', { status: 503, statusText: 'Unavailable' });
}, async () => {
const defaults = defaultsFor({
env: {
WORKFORCE_WORKSPACE_TOKEN: 'workspace-token',
WORKFORCE_CLOUD_BASE_URL: 'https://cloud.example.test'
}
});
const handle = await defaults.team!.spawn({ task: 'exhaust', members: [] });

// Budget is exhausted after MAX_TRANSIENT_ERRORS retries; the next retryable
// failure is rethrown verbatim (not converted into the deadline-timeout error).
await assert.rejects(
() => handle.completion(),
/ctx\.team\.attach\("team_exhaust"\)\.status\(\): 503 Unavailable - try again/
);
// MAX_TRANSIENT_ERRORS (3) retries that continue, then one more poll that throws.
assert.equal(statusCalls, 4);
});
});

function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
return new Response(JSON.stringify(body), {
...init,
headers: {
'content-type': 'application/json',
...init.headers
}
});
}

// Safe under node:test because top-level tests run sequentially by default
// (no `{ concurrency: true }` here), so the global swap never overlaps; the
// `finally` restore guarantees the original fetch is back even on failure.
async function withFetch(
fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> | Response,
run: () => Promise<void>
): Promise<void> {
const originalFetch = globalThis.fetch;
globalThis.fetch = fetchImpl as typeof fetch;
try {
await run();
} finally {
globalThis.fetch = originalFetch;
}
Comment on lines +348 to +358
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js node:test, what are the default concurrency semantics for top-level tests in a single file, and what is the recommended way to safely mock globalThis.fetch per test?

💡 Result:

Default concurrency semantics for top-level tests in a single file (node:test) 1) Top-level tests (in a file) are not run in parallel by default. When process-level test isolation is disabled (the default), Node loads each test file into the same runner process, and “top level tests are executed with a concurrency of one.”[1] 2) Subtests are sequential unless you opt into parallelism. The node:test test option “concurrency” defaults to false, meaning “only one test runs at a time.” If concurrency is unspecified, subtests inherit this value from their parent; the default therefore keeps them sequential within the suite unless you set concurrency explicitly.[2][1] (How this changes with process-level isolation) If process-level test isolation is enabled, each test file runs in a separate child process; the number of child processes in parallel is controlled by --test-concurrency.[2] Recommended way to safely mock globalThis.fetch per test Use node:test’s built-in per-test mocking utilities (module mocks), rather than overwriting globalThis.fetch in shared global state. Specifically, register the mock before importing the module that uses fetch, and then dynamically import the module under test. The node:test docs/example show using mock.module(...) and then doing await import(...) after the mock is registered, ensuring each test gets its own mocked implementation in the right order.[3] Why: because when files share a context (no process isolation), tests can modify global state and affect other tests.[1] Mocking fetch via node:test’s module-mock mechanism avoids relying on a shared globalThis.fetch across concurrent tests. Practical pattern (sketch) - Inside your test callback: - call mock.module(...) to replace the module (or export) that provides fetch usage - then dynamically import the module under test If you truly must set globalThis.fetch, ensure you restore it in the same test (e.g., within afterEach / finally), because global mutation is inherently shared when isolation is off.[1]

Citations:


Avoid shared global fetch swapping—use per-test mocking instead.

In packages/runtime/src/cloud-defaults.test.ts (withFetch, lines 345-355), globalThis.fetch is replaced for the duration of run. node:test executes top-level tests in a single file with concurrency=1 by default, so same-file overlap is unlikely—but this is still brittle if test/process concurrency settings change or if other tests in the same process also touch globalThis.fetch. Prefer node:test’s per-test module mocking (e.g., mock.module(...) + dynamic import) or dependency injection so tests don’t rely on process-global mutation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/runtime/src/cloud-defaults.test.ts` around lines 345 - 355, The
helper withFetch replaces globalThis.fetch for the whole process which is
brittle; instead change tests that use withFetch to mock fetch per-test using
node:test's mock.module + dynamic import or inject a fetch implementation into
the module under test. Locate uses of withFetch and the function withFetch
itself, remove the global swap, and rewrite each test to call mock.module(() =>
{ globalThis.fetch is not mutated }) or import the module under a mock context
and pass a fake fetch via dependency injection (e.g., passFetch or setFetch API)
so the fetch override scope is limited to that test module and no process-global
mutation occurs.

}
Loading
Loading