feat(mcp-workforce): MCP server bridging harnesses to workforce primitives#91
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (15)
📝 WalkthroughWalkthroughAdds a new ChangesMCP Workforce Server Package
VFS-backed Integration Clients & Request Layer
sequenceDiagram
participant MCP as MCP Server
participant DISP as dispatchIntegration
participant CLIENT as Integration Client (e.g., GitHub)
participant VFS as Relayfile Mount (filesystem)
participant WB as Writeback Worker
MCP->>DISP: call integration.github.createIssue(args)
DISP->>CLIENT: resolve/create client with relayfileMountRoot
CLIENT->>VFS: write draft JSON via writeJsonFile (atomic write)
VFS->>WB: (external) writeback worker detects draft -> performs GitHub API call
WB->>VFS: write receipt file under same path
CLIENT->>VFS: poll/read receipt (waitForReceipt) and return result to DISP
DISP->>MCP: return MCP jsonResult(...) to caller
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
| content: [ | ||
| { | ||
| type: 'text' as const, | ||
| text: JSON.stringify(value) |
There was a problem hiding this comment.
🔴 JSON.stringify(undefined) produces undefined instead of a string for postReview tool response
The GithubClient.postReview() method returns Promise<void> (packages/runtime/src/clients/github.ts:54), so dispatchIntegration('integration.github.postReview', ...) resolves to undefined. This undefined is passed to jsonResult(), which calls JSON.stringify(undefined). In JavaScript, JSON.stringify(undefined) returns the primitive undefined — not the string "undefined". This means the MCP response content item becomes {type: "text", text: undefined}, which when serialized over the wire drops the text key entirely ({"type":"text"}), violating the MCP protocol's requirement that text content includes a text string.
The effect is that a successful postReview call (review actually posted on GitHub) returns a malformed MCP response, which could cause the MCP client to error or the AI agent to believe the operation failed and retry, posting duplicate reviews.
| text: JSON.stringify(value) | |
| text: JSON.stringify(value ?? null) |
Was this helpful? React with 👍 or 👎 to provide feedback.
`integration.github.postReview` is a void-returning tool. The jsonResult
helper called `JSON.stringify(value)` which, when value is undefined,
returns `undefined` (not a string) — the resulting MCP CallToolResult
failed its content-shape check on the wire.
Normalize undefined to a `{ ok: true }` sentinel before stringifying so
void-returning tools still emit parseable JSON. `null` continues to
round-trip as the JSON `null` literal; only `undefined` triggers the
sentinel swap.
`jsonResult` is now exported so the regression test can call it
directly rather than synthesizing a full MCP transport.
Flagged by Devin Review on #91.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`integration.github.postReview` is a void-returning tool. The jsonResult
helper called `JSON.stringify(value)` which, when value is undefined,
returns `undefined` (not a string) — the resulting MCP CallToolResult
failed its content-shape check on the wire.
Normalize undefined to a `{ ok: true }` sentinel before stringifying so
void-returning tools still emit parseable JSON. `null` continues to
round-trip as the JSON `null` literal; only `undefined` triggers the
sentinel swap.
`jsonResult` is now exported so the regression test can call it
directly rather than synthesizing a full MCP transport.
Flagged by Devin Review on #91.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7774bbd to
fda8995
Compare
`integration.github.postReview` is a void-returning tool. The jsonResult
helper called `JSON.stringify(value)` which, when value is undefined,
returns `undefined` (not a string) — the resulting MCP CallToolResult
failed its content-shape check on the wire.
Normalize undefined to a `{ ok: true }` sentinel before stringifying so
void-returning tools still emit parseable JSON. `null` continues to
round-trip as the JSON `null` literal; only `undefined` triggers the
sentinel swap.
`jsonResult` is now exported so the regression test can call it
directly rather than synthesizing a full MCP transport.
Flagged by Devin Review on #91.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fda8995 to
2bd3291
Compare
`integration.github.postReview` is a void-returning tool. The jsonResult
helper called `JSON.stringify(value)` which, when value is undefined,
returns `undefined` (not a string) — the resulting MCP CallToolResult
failed its content-shape check on the wire.
Normalize undefined to a `{ ok: true }` sentinel before stringifying so
void-returning tools still emit parseable JSON. `null` continues to
round-trip as the JSON `null` literal; only `undefined` triggers the
sentinel swap.
`jsonResult` is now exported so the regression test can call it
directly rather than synthesizing a full MCP transport.
Flagged by Devin Review on #91.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2bd3291 to
a227c47
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/mcp-workforce/src/tools/memory.test.ts (1)
130-139: ⚡ Quick winConsider adding a decimal-limit rejection case
To pin down limit semantics, add a test for non-integer limits (e.g.,
1.5) in the same range-validation block.🤖 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/mcp-workforce/src/tools/memory.test.ts` around lines 130 - 139, Add a test to reject non-integer limits by asserting memoryRecall({ query: 'q', limit: 1.5 }, { config: config() }) throws the same validation error; update the existing test block around memoryRecall to include a decimal-limit case that expects /"limit" must be 1-50/ so the range/precision semantics are covered.packages/mcp-workforce/src/tools/memory.ts (1)
131-136: ⚡ Quick winSanitize returned scope before assigning
PersonaMemoryScope
entry.metadata?.scopecomes from external data and is currently trusted as-is. Guard it against unknown values and fallback to'workspace'to keep the output contract sound.Suggested patch
const results = payload.results ?? []; const items: MemoryItem[] = results.map((entry) => ({ @@ - scope: entry.metadata?.scope ?? 'workspace', + scope: VALID_SCOPES.has((entry.metadata?.scope ?? 'workspace') as PersonaMemoryScope) + ? ((entry.metadata?.scope ?? 'workspace') as PersonaMemoryScope) + : 'workspace', createdAt: entry.createdAt ?? entry.created_at ?? '' }));🤖 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/mcp-workforce/src/tools/memory.ts` around lines 131 - 136, The scope assigned in the items mapping (inside the MemoryItem creation) trusts entry.metadata?.scope directly; validate that value against the allowed PersonaMemoryScope values (or an equivalent set/enum) before assigning it to scope and fall back to 'workspace' for unknown/undefined values. Update the mapping where MemoryItem is constructed (the content block using entry.metadata?.scope) to check membership (e.g., isValidPersonaMemoryScope(scope) or compare against the PersonaMemoryScope enum) and only use entry.metadata.scope when valid, otherwise use 'workspace'.packages/mcp-workforce/src/tools/integrations.test.ts (1)
77-104: ⚡ Quick winAdd regression tests for integer-only numeric fields
Given
target.numberand reviewcomment.lineare numeric identifiers, add rejects for decimal/negative values to lock validation behavior.Example test cases
+test('dispatchIntegration rejects non-integer github numeric fields', async () => { + _resetIntegrationCache(); + await assert.rejects( + () => + dispatchIntegration( + 'integration.github.getPr', + { target: { owner: 'o', repo: 'r', number: 1.5 } }, + { config: config() } + ), + /target\.number: must be a positive integer/ + ); +});🤖 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/mcp-workforce/src/tools/integrations.test.ts` around lines 77 - 104, Add regression tests to ensure numeric identifier fields reject decimals and negatives: extend the existing tests around dispatchIntegration (and use _resetIntegrationCache) to assert.rejects when passing non-integer values for target.number (e.g., 1.5 and -1) in the 'integration.github.postReview' payload and when passing non-integer values for review.comment.line (e.g., 2.2 and -3) in relevant integration payloads; each test should call dispatchIntegration with { config: config() } and assert the error message indicates the field must be an integer (or match the current validation error format) so validation enforces integer-only numeric IDs.packages/mcp-workforce/src/index.ts (1)
18-24: ⚡ Quick winAvoid exporting
_resetIntegrationCachefrom the public barrel.This looks test-oriented and becomes part of the semver contract once exported publicly. Prefer keeping it internal (or exporting from a clearly internal/testing-only subpath).
Suggested change
export { dispatchIntegration, INTEGRATION_TOOL_NAMES, - _resetIntegrationCache, type IntegrationToolDeps, type IntegrationToolName } from './tools/integrations.js';🤖 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/mcp-workforce/src/index.ts` around lines 18 - 24, The public barrel is exporting a test/internal helper (_resetIntegrationCache) which should not be part of the public API; remove _resetIntegrationCache from the export list (leave dispatchIntegration, INTEGRATION_TOOL_NAMES, and the types) so it is not part of the semver contract, keep the actual _resetIntegrationCache implementation internal in the integrations module, and if tests need it, re-export it from a clearly-named internal/test-only entry (or update tests to import it from the integrations module directly) rather than exposing it from the public barrel.
🤖 Prompt for all review comments with 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.
Inline comments:
In `@packages/mcp-workforce/src/tools/integrations.ts`:
- Around line 151-175: The number and string validators are too permissive:
update asNumber to require a finite integer >= 1 (reject decimals, negatives,
zero) and throw a clear error using the provided label; update asNonEmptyString
to trim the input before validation and return the trimmed string so callers get
normalized values; ensure asTarget continues to call asNonEmptyString for
owner/repo so it receives trimmed values and asNumber for number so it receives
a positive integer (adjust error text if needed to mention "positive integer"
for target.number).
In `@packages/mcp-workforce/src/tools/memory.ts`:
- Around line 99-102: The current validation for args.limit in memory.recall
allows non-integer values like 2.5; update the validation to require an integer
by checking Number.isInteger(limit) (replace or augment Number.isFinite check)
and throw the same or clearer error if it's not an integer or out of range;
reference the local variable limit and the memory.recall usage so you modify the
check around "const limit = args.limit ?? 5" to use Number.isInteger(limit) &&
limit >= 1 && limit <= 50 (otherwise throw).
In `@packages/mcp-workforce/src/tools/workflow.ts`:
- Around line 34-42: Both fetch calls (the local variable fetchImpl used to POST
to url in this file and the other fetch later) can hang indefinitely; wrap each
fetch call with an AbortController-based timeout: create an AbortController,
start a setTimeout that calls controller.abort() after a configurable timeout
(e.g., deps.config.requestTimeoutMs or a default), pass controller.signal to
fetchImpl(...) as the signal option, clear the timeout once the promise
resolves, and handle the AbortError to throw a clear timeout error. Update the
fetch invocation that builds the request body (where fetchImpl is called) and
the later fetch call to use this abort-timeout wrapper so stalled network
requests are aborted cleanly.
---
Nitpick comments:
In `@packages/mcp-workforce/src/index.ts`:
- Around line 18-24: The public barrel is exporting a test/internal helper
(_resetIntegrationCache) which should not be part of the public API; remove
_resetIntegrationCache from the export list (leave dispatchIntegration,
INTEGRATION_TOOL_NAMES, and the types) so it is not part of the semver contract,
keep the actual _resetIntegrationCache implementation internal in the
integrations module, and if tests need it, re-export it from a clearly-named
internal/test-only entry (or update tests to import it from the integrations
module directly) rather than exposing it from the public barrel.
In `@packages/mcp-workforce/src/tools/integrations.test.ts`:
- Around line 77-104: Add regression tests to ensure numeric identifier fields
reject decimals and negatives: extend the existing tests around
dispatchIntegration (and use _resetIntegrationCache) to assert.rejects when
passing non-integer values for target.number (e.g., 1.5 and -1) in the
'integration.github.postReview' payload and when passing non-integer values for
review.comment.line (e.g., 2.2 and -3) in relevant integration payloads; each
test should call dispatchIntegration with { config: config() } and assert the
error message indicates the field must be an integer (or match the current
validation error format) so validation enforces integer-only numeric IDs.
In `@packages/mcp-workforce/src/tools/memory.test.ts`:
- Around line 130-139: Add a test to reject non-integer limits by asserting
memoryRecall({ query: 'q', limit: 1.5 }, { config: config() }) throws the same
validation error; update the existing test block around memoryRecall to include
a decimal-limit case that expects /"limit" must be 1-50/ so the range/precision
semantics are covered.
In `@packages/mcp-workforce/src/tools/memory.ts`:
- Around line 131-136: The scope assigned in the items mapping (inside the
MemoryItem creation) trusts entry.metadata?.scope directly; validate that value
against the allowed PersonaMemoryScope values (or an equivalent set/enum) before
assigning it to scope and fall back to 'workspace' for unknown/undefined values.
Update the mapping where MemoryItem is constructed (the content block using
entry.metadata?.scope) to check membership (e.g.,
isValidPersonaMemoryScope(scope) or compare against the PersonaMemoryScope enum)
and only use entry.metadata.scope when valid, otherwise use 'workspace'.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b039c908-2606-41de-abec-c5774aadcd6a
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (15)
packages/mcp-workforce/README.mdpackages/mcp-workforce/package.jsonpackages/mcp-workforce/src/bin.tspackages/mcp-workforce/src/config.test.tspackages/mcp-workforce/src/config.tspackages/mcp-workforce/src/index.tspackages/mcp-workforce/src/server.test.tspackages/mcp-workforce/src/server.tspackages/mcp-workforce/src/tools/integrations.test.tspackages/mcp-workforce/src/tools/integrations.tspackages/mcp-workforce/src/tools/memory.test.tspackages/mcp-workforce/src/tools/memory.tspackages/mcp-workforce/src/tools/workflow.test.tspackages/mcp-workforce/src/tools/workflow.tspackages/mcp-workforce/tsconfig.json
| function asNonEmptyString(value: unknown, label: string): string { | ||
| if (typeof value !== 'string' || !value.trim()) { | ||
| throw new Error(`${label}: must be a non-empty string`); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| function asNumber(value: unknown, label: string): number { | ||
| if (typeof value !== 'number' || !Number.isFinite(value)) { | ||
| throw new Error(`${label}: must be a finite number`); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| function isString(value: unknown): value is string { | ||
| return typeof value === 'string'; | ||
| } | ||
|
|
||
| function asTarget(value: unknown): { owner: string; repo: string; number: number } { | ||
| const obj = asObject(value, 'target'); | ||
| return { | ||
| owner: asNonEmptyString(obj.owner, 'target.owner'), | ||
| repo: asNonEmptyString(obj.repo, 'target.repo'), | ||
| number: asNumber(obj.number, 'target.number') | ||
| }; |
There was a problem hiding this comment.
Harden argument normalization for GitHub identifiers and strings
asNumber currently accepts decimals/negatives (e.g., 1.5, -3), and asNonEmptyString returns untrimmed values after validation. That can generate invalid GitHub endpoints/payloads and harder-to-diagnose API failures.
Suggested patch
function asNonEmptyString(value: unknown, label: string): string {
- if (typeof value !== 'string' || !value.trim()) {
+ if (typeof value !== 'string' || !value.trim()) {
throw new Error(`${label}: must be a non-empty string`);
}
- return value;
+ return value.trim();
}
-function asNumber(value: unknown, label: string): number {
- if (typeof value !== 'number' || !Number.isFinite(value)) {
- throw new Error(`${label}: must be a finite number`);
+function asPositiveInteger(value: unknown, label: string): number {
+ if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
+ throw new Error(`${label}: must be a positive integer`);
}
return value;
}
@@
path: asNonEmptyString(c.path, 'comment.path'),
- line: asNumber(c.line, 'comment.line'),
+ line: asPositiveInteger(c.line, 'comment.line'),
body: asNonEmptyString(c.body, 'comment.body')
}))
@@
owner: asNonEmptyString(obj.owner, 'target.owner'),
repo: asNonEmptyString(obj.repo, 'target.repo'),
- number: asNumber(obj.number, 'target.number')
+ number: asPositiveInteger(obj.number, 'target.number')
};
}🤖 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/mcp-workforce/src/tools/integrations.ts` around lines 151 - 175, The
number and string validators are too permissive: update asNumber to require a
finite integer >= 1 (reject decimals, negatives, zero) and throw a clear error
using the provided label; update asNonEmptyString to trim the input before
validation and return the trimmed string so callers get normalized values;
ensure asTarget continues to call asNonEmptyString for owner/repo so it receives
trimmed values and asNumber for number so it receives a positive integer (adjust
error text if needed to mention "positive integer" for target.number).
| const limit = args.limit ?? 5; | ||
| if (!Number.isFinite(limit) || limit <= 0 || limit > 50) { | ||
| throw new Error('memory.recall: "limit" must be 1-50'); | ||
| } |
There was a problem hiding this comment.
Enforce integer limit in memory.recall
Current check allows decimals (e.g., 2.5). Since limit is a count, enforce integers to avoid downstream API errors or implicit coercion.
Suggested patch
- if (!Number.isFinite(limit) || limit <= 0 || limit > 50) {
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 50) {
throw new Error('memory.recall: "limit" must be 1-50');
}🤖 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/mcp-workforce/src/tools/memory.ts` around lines 99 - 102, The
current validation for args.limit in memory.recall allows non-integer values
like 2.5; update the validation to require an integer by checking
Number.isInteger(limit) (replace or augment Number.isFinite check) and throw the
same or clearer error if it's not an integer or out of range; reference the
local variable limit and the memory.recall usage so you modify the check around
"const limit = args.limit ?? 5" to use Number.isInteger(limit) && limit >= 1 &&
limit <= 50 (otherwise throw).
| const fetchImpl = deps.fetchImpl ?? fetch; | ||
| const url = `${deps.config.cloudUrl}/api/v1/workspaces/${encodeURIComponent( | ||
| deps.config.workspaceId | ||
| )}/workflows/run`; | ||
| const response = await fetchImpl(url, { | ||
| method: 'POST', | ||
| headers: workflowHeaders(deps.config), | ||
| body: JSON.stringify({ name: args.name, args: args.args ?? {} }) | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n "fetchImpl\\(" packages/mcp-workforce/src/tools/workflow.ts
rg -n "signal\\s*:" packages/mcp-workforce/src/tools/workflow.tsRepository: AgentWorkforce/workforce
Length of output: 158
🏁 Script executed:
cat -n packages/mcp-workforce/src/tools/workflow.ts | head -100Repository: AgentWorkforce/workforce
Length of output: 3926
🏁 Script executed:
cat packages/mcp-workforce/package.json | jq '.engines'Repository: AgentWorkforce/workforce
Length of output: 73
🏁 Script executed:
rg -n "AbortController\|AbortSignal\|timeout" packages/mcp-workforce/src/tools/workflow.tsRepository: AgentWorkforce/workforce
Length of output: 50
🏁 Script executed:
cat packages/mcp-workforce/package.json | head -30Repository: AgentWorkforce/workforce
Length of output: 690
🏁 Script executed:
cat packages/package.json 2>/dev/null | jq '.engines' || echo "No package.json at root"Repository: AgentWorkforce/workforce
Length of output: 50
🏁 Script executed:
find . -name "package.json" -type f | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && cat {} | jq ".engines" 2>/dev/null || echo "no engines field"'Repository: AgentWorkforce/workforce
Length of output: 300
🏁 Script executed:
cat packages/mcp-workforce/tsconfig.json 2>/dev/null | jq '.compilerOptions | {target, lib}' || echo "No tsconfig or no compiler options"Repository: AgentWorkforce/workforce
Length of output: 104
Add explicit timeouts to workflow HTTP calls.
Both external fetches at lines 38 and 65 can block indefinitely on stalled network conditions. Implement an abort timeout wrapper to prevent hanging requests that tie up harness execution.
Suggested change
+const WORKFLOW_HTTP_TIMEOUT_MS = 30_000;
+
+async function fetchWithTimeout(
+ fetchImpl: typeof fetch,
+ input: string,
+ init: RequestInit
+): Promise<Response> {
+ const controller = new AbortController();
+ const timer = setTimeout(() => controller.abort(), WORKFLOW_HTTP_TIMEOUT_MS);
+ try {
+ return await fetchImpl(input, { ...init, signal: controller.signal });
+ } finally {
+ clearTimeout(timer);
+ }
+}
+
export async function workflowRun(
args: { name: string; args?: Record<string, unknown> },
deps: WorkflowToolDeps
): Promise<WorkflowRunResult> {
@@
- const response = await fetchImpl(url, {
+ const response = await fetchWithTimeout(fetchImpl, url, {
method: 'POST',
headers: workflowHeaders(deps.config),
body: JSON.stringify({ name: args.name, args: args.args ?? {} })
});
@@
export async function workflowStatus(
args: { runId: string },
deps: WorkflowToolDeps
): Promise<WorkflowStatusResult> {
@@
- const response = await fetchImpl(url, {
+ const response = await fetchWithTimeout(fetchImpl, url, {
method: 'GET',
headers: workflowHeaders(deps.config)
});🤖 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/mcp-workforce/src/tools/workflow.ts` around lines 34 - 42, Both
fetch calls (the local variable fetchImpl used to POST to url in this file and
the other fetch later) can hang indefinitely; wrap each fetch call with an
AbortController-based timeout: create an AbortController, start a setTimeout
that calls controller.abort() after a configurable timeout (e.g.,
deps.config.requestTimeoutMs or a default), pass controller.signal to
fetchImpl(...) as the signal option, clear the timeout once the promise
resolves, and handle the AbortError to throw a clear timeout error. Update the
fetch invocation that builds the request body (where fetchImpl is called) and
the later fetch call to use this abort-timeout wrapper so stalled network
requests are aborted cleanly.
`integration.github.postReview` is a void-returning tool. The jsonResult
helper called `JSON.stringify(value)` which, when value is undefined,
returns `undefined` (not a string) — the resulting MCP CallToolResult
failed its content-shape check on the wire.
Normalize undefined to a `{ ok: true }` sentinel before stringifying so
void-returning tools still emit parseable JSON. `null` continues to
round-trip as the JSON `null` literal; only `undefined` triggers the
sentinel swap.
`jsonResult` is now exported so the regression test can call it
directly rather than synthesizing a full MCP transport.
Flagged by Devin Review on #91.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a227c47 to
b509753
Compare
…t style
Switches workforce's integration clients from direct REST calls to the
Relayfile-VFS writeback pattern used by sage + the cloud workflows.
Handler-side surface (ctx.github.upsertIssue, ctx.linear.comment, etc.)
stays identical; the wire underneath flips from "speak HTTP to GitHub"
to "write a JSON draft inside the Relayfile mount and let the writeback
worker do the actual API call." Aligns workforce with the rest of the
org's integration story and inherits writeback durability + retry for
free.
Substrate
- packages/runtime/src/errors.ts (top-level): WorkforceIntegrationError
moves here with the { provider, operation, cause, retryable } shape
sage/cloud already use. Old clients/errors.ts is removed; the public
surface re-exports it from the same package import path so existing
consumers (mcp-workforce) keep compiling.
- packages/runtime/src/clients/request.ts: shared VFS helpers
(readJsonFile, readTextFile, listJsonFiles, listDirectoryEntries,
writeJsonFile + atomic write-then-rename) with mount-root path
validation and optional writeback-receipt polling.
Clients
- github.ts is rewritten as a VFS client. Same GithubClient interface
(comment, createIssue, upsertIssue, getPr, postReview); each method
now reads/writes files at canonical paths under
`/github/repos/<owner>/<repo>/...`.
- linear, slack, notion, jira ship as new typed clients with the same
pattern. IntegrationClients in types.ts now types all five concretely
instead of leaving four as unknown.
Tests
- github.test.ts is rewritten end-to-end against a tempdir mount.
- linear/slack/notion/jira tests run against tempdir mounts too.
- 29 runtime tests pass (up from 18), 386 across the repo.
Example
- weekly-digest/agent.ts drops the WORKFORCE_INTEGRATION_GITHUB_TOKEN
plumbing; the github client picks up RELAYFILE_MOUNT_ROOT instead.
- weekly-digest/README.md documents the writeback model + Relayfile
mount env requirement, and drops the GITHUB_TOKEN setup step.
Notes
- mcp-workforce (PR #91) imports createGithubClient with a different
construction shape today (`{ token }`); it'll need a follow-up
commit to switch to IntegrationClientOptions once this lands. The
MCP package depends on the new shape, not the old.
- The direct-REST github implementation that shipped in #90 is
replaced wholesale. No persona today depends on it; weekly-digest
is updated in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-on to the persona-tier flatten refactor — the openclaw-routing example still read from selection.runtime.* and selection.tier, which no longer exist after PersonaSelection lost its per-tier wrapper. Required to keep the examples typecheck green on top of the flatten.
efb115b to
1f8dcdb
Compare
…tives
Ships Track C of the workforce-deploy-v1 cross-repo plan
(workforce/docs/plans/deploy-v1-workflow-spec.md). New package
@agentworkforce/mcp-workforce exposes nine tools over stdio MCP:
workflow.run, workflow.status
memory.save, memory.recall
integration.github.{comment,createIssue,upsertIssue,getPr,postReview}
Design notes
- Stdio transport via @modelcontextprotocol/sdk so the harness's
built-in MCP client wires up with `npx @agentworkforce/mcp-workforce`.
- Workflow tools POST/GET against the workforce cloud workflows REST
API using the workspace token the runtime injects via
WORKFORCE_RUNTIME_TOKEN.
- Memory tools talk directly to the Supermemory REST API
(workspace-scoped container tag, scope tags) so the package stays
free of heavy adapter deps. Workspace memories are visible across
sage and other consumers that use the same container shape.
- Integration tools delegate to @agentworkforce/runtime/clients
(currently github only); a single dispatcher splits tool names
like `integration.github.comment` and lazy-constructs the client
from WORKFORCE_INTEGRATION_GITHUB_TOKEN.
- Config loader (loadConfig) trims env values and enforces
WORKFORCE_WORKSPACE_ID as the only hard requirement at startup —
per-tool deps (token, supermemory key) check at call time so
partial wiring is debuggable.
Tests
- 21 tests across config, workflow, memory, integrations, and server
registration. Workflow + memory tests use a deterministic fakeFetch;
integrations test uses a globalThis.fetch override to drive the real
GithubClient end-to-end.
Persona-side wiring
- Runtime is expected to inject the server automatically when
ctx.harness.run spawns a harness. Power users can declare it
manually in persona.mcpServers as documented in the README.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`integration.github.postReview` is a void-returning tool. The jsonResult
helper called `JSON.stringify(value)` which, when value is undefined,
returns `undefined` (not a string) — the resulting MCP CallToolResult
failed its content-shape check on the wire.
Normalize undefined to a `{ ok: true }` sentinel before stringifying so
void-returning tools still emit parseable JSON. `null` continues to
round-trip as the JSON `null` literal; only `undefined` triggers the
sentinel swap.
`jsonResult` is now exported so the regression test can call it
directly rather than synthesizing a full MCP transport.
Flagged by Devin Review on #91.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the MCP server with the runtime's new client shape from #92. Before: the integration tools called createGithubClient({ token }) using WORKFORCE_INTEGRATION_GITHUB_TOKEN. That signature no longer exists in the runtime now that github speaks Relayfile-VFS, so the MCP package would fail to compile once #92 lands. Config (config.ts) - WorkforceMcpConfig drops `providerTokens`. The MCP server is no longer the place to hold provider PATs — Relayfile holds the credentials and the writeback worker uses them. - New `relayfileMountRoot` field, populated from RELAYFILE_MOUNT_ROOT (with RELAYFILE_ROOT as a legacy alias). The workforce runtime sets these env vars automatically when it spawns the harness via ctx.harness.run. - New `writebackTimeoutMs` field (default 30s, overridable via WORKFORCE_WRITEBACK_TIMEOUT_MS). Passed straight through to integration clients so handlers that need a synchronous receipt pay the same wait the runtime would. Integration dispatcher (tools/integrations.ts) - resolveGithub() now constructs createGithubClient with { relayfileMountRoot, writebackTimeoutMs } from config instead of a token. Refuses to construct when the mount root is missing with a message pointing at the runtime contract. Tests - config.test exercises RELAYFILE_MOUNT_ROOT (+ RELAYFILE_ROOT alias) and WORKFORCE_WRITEBACK_TIMEOUT_MS overrides. - integrations.test now writes against a tempdir mount and reads the resulting draft JSON file back to assert the canonical shape Relayfile expects. Covers happy path, missing-mount-root failure, postReview enum validation, and field-pointed errors. - server.test loads config with RELAYFILE_MOUNT_ROOT instead of a github PAT. - workflow.test + memory.test drop the obsolete providerTokens fixture key. 23 mcp-workforce tests pass (up from 21). PR base flips from main to feat/integrations-vfs (PR #92) since the new client shape lives there. Auto-rebases to main when #92 merges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The README still documented WORKFORCE_INTEGRATION_GITHUB_TOKEN as the setup step for integration.* tools, but those tools no longer take a provider token — they write JSON drafts into the Relayfile mount and Relayfile's writeback worker handles the actual provider call. Updated the stand-alone setup example and the config table to point at RELAYFILE_MOUNT_ROOT (with the RELAYFILE_ROOT alias and the new WORKFORCE_WRITEBACK_TIMEOUT_MS override documented for completeness). Adds a sentence on the writeback model so readers understand why the MCP server never sees a provider token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c2355fe to
d8654e4
Compare
…t style
Switches workforce's integration clients from direct REST calls to the
Relayfile-VFS writeback pattern used by sage + the cloud workflows.
Handler-side surface (ctx.github.upsertIssue, ctx.linear.comment, etc.)
stays identical; the wire underneath flips from "speak HTTP to GitHub"
to "write a JSON draft inside the Relayfile mount and let the writeback
worker do the actual API call." Aligns workforce with the rest of the
org's integration story and inherits writeback durability + retry for
free.
Substrate
- packages/runtime/src/errors.ts (top-level): WorkforceIntegrationError
moves here with the { provider, operation, cause, retryable } shape
sage/cloud already use. Old clients/errors.ts is removed; the public
surface re-exports it from the same package import path so existing
consumers (mcp-workforce) keep compiling.
- packages/runtime/src/clients/request.ts: shared VFS helpers
(readJsonFile, readTextFile, listJsonFiles, listDirectoryEntries,
writeJsonFile + atomic write-then-rename) with mount-root path
validation and optional writeback-receipt polling.
Clients
- github.ts is rewritten as a VFS client. Same GithubClient interface
(comment, createIssue, upsertIssue, getPr, postReview); each method
now reads/writes files at canonical paths under
`/github/repos/<owner>/<repo>/...`.
- linear, slack, notion, jira ship as new typed clients with the same
pattern. IntegrationClients in types.ts now types all five concretely
instead of leaving four as unknown.
Tests
- github.test.ts is rewritten end-to-end against a tempdir mount.
- linear/slack/notion/jira tests run against tempdir mounts too.
- 29 runtime tests pass (up from 18), 386 across the repo.
Example
- weekly-digest/agent.ts drops the WORKFORCE_INTEGRATION_GITHUB_TOKEN
plumbing; the github client picks up RELAYFILE_MOUNT_ROOT instead.
- weekly-digest/README.md documents the writeback model + Relayfile
mount env requirement, and drops the GITHUB_TOKEN setup step.
Notes
- mcp-workforce (PR #91) imports createGithubClient with a different
construction shape today (`{ token }`); it'll need a follow-up
commit to switch to IntegrationClientOptions once this lands. The
MCP package depends on the new shape, not the old.
- The direct-REST github implementation that shipped in #90 is
replaced wholesale. No persona today depends on it; weekly-digest
is updated in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1f8dcdb to
1225e04
Compare
…t style
Switches workforce's integration clients from direct REST calls to the
Relayfile-VFS writeback pattern used by sage + the cloud workflows.
Handler-side surface (ctx.github.upsertIssue, ctx.linear.comment, etc.)
stays identical; the wire underneath flips from "speak HTTP to GitHub"
to "write a JSON draft inside the Relayfile mount and let the writeback
worker do the actual API call." Aligns workforce with the rest of the
org's integration story and inherits writeback durability + retry for
free.
Substrate
- packages/runtime/src/errors.ts (top-level): WorkforceIntegrationError
moves here with the { provider, operation, cause, retryable } shape
sage/cloud already use. Old clients/errors.ts is removed; the public
surface re-exports it from the same package import path so existing
consumers (mcp-workforce) keep compiling.
- packages/runtime/src/clients/request.ts: shared VFS helpers
(readJsonFile, readTextFile, listJsonFiles, listDirectoryEntries,
writeJsonFile + atomic write-then-rename) with mount-root path
validation and optional writeback-receipt polling.
Clients
- github.ts is rewritten as a VFS client. Same GithubClient interface
(comment, createIssue, upsertIssue, getPr, postReview); each method
now reads/writes files at canonical paths under
`/github/repos/<owner>/<repo>/...`.
- linear, slack, notion, jira ship as new typed clients with the same
pattern. IntegrationClients in types.ts now types all five concretely
instead of leaving four as unknown.
Tests
- github.test.ts is rewritten end-to-end against a tempdir mount.
- linear/slack/notion/jira tests run against tempdir mounts too.
- 29 runtime tests pass (up from 18), 386 across the repo.
Example
- weekly-digest/agent.ts drops the WORKFORCE_INTEGRATION_GITHUB_TOKEN
plumbing; the github client picks up RELAYFILE_MOUNT_ROOT instead.
- weekly-digest/README.md documents the writeback model + Relayfile
mount env requirement, and drops the GITHUB_TOKEN setup step.
Notes
- mcp-workforce (PR #91) imports createGithubClient with a different
construction shape today (`{ token }`); it'll need a follow-up
commit to switch to IntegrationClientOptions once this lands. The
MCP package depends on the new shape, not the old.
- The direct-REST github implementation that shipped in #90 is
replaced wholesale. No persona today depends on it; weekly-digest
is updated in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1225e04 to
af8f4a2
Compare
…t style
Switches workforce's integration clients from direct REST calls to the
Relayfile-VFS writeback pattern used by sage + the cloud workflows.
Handler-side surface (ctx.github.upsertIssue, ctx.linear.comment, etc.)
stays identical; the wire underneath flips from "speak HTTP to GitHub"
to "write a JSON draft inside the Relayfile mount and let the writeback
worker do the actual API call." Aligns workforce with the rest of the
org's integration story and inherits writeback durability + retry for
free.
Substrate
- packages/runtime/src/errors.ts (top-level): WorkforceIntegrationError
moves here with the { provider, operation, cause, retryable } shape
sage/cloud already use. Old clients/errors.ts is removed; the public
surface re-exports it from the same package import path so existing
consumers (mcp-workforce) keep compiling.
- packages/runtime/src/clients/request.ts: shared VFS helpers
(readJsonFile, readTextFile, listJsonFiles, listDirectoryEntries,
writeJsonFile + atomic write-then-rename) with mount-root path
validation and optional writeback-receipt polling.
Clients
- github.ts is rewritten as a VFS client. Same GithubClient interface
(comment, createIssue, upsertIssue, getPr, postReview); each method
now reads/writes files at canonical paths under
`/github/repos/<owner>/<repo>/...`.
- linear, slack, notion, jira ship as new typed clients with the same
pattern. IntegrationClients in types.ts now types all five concretely
instead of leaving four as unknown.
Tests
- github.test.ts is rewritten end-to-end against a tempdir mount.
- linear/slack/notion/jira tests run against tempdir mounts too.
- 29 runtime tests pass (up from 18), 386 across the repo.
Example
- weekly-digest/agent.ts drops the WORKFORCE_INTEGRATION_GITHUB_TOKEN
plumbing; the github client picks up RELAYFILE_MOUNT_ROOT instead.
- weekly-digest/README.md documents the writeback model + Relayfile
mount env requirement, and drops the GITHUB_TOKEN setup step.
Notes
- mcp-workforce (PR #91) imports createGithubClient with a different
construction shape today (`{ token }`); it'll need a follow-up
commit to switch to IntegrationClientOptions once this lands. The
MCP package depends on the new shape, not the old.
- The direct-REST github implementation that shipped in #90 is
replaced wholesale. No persona today depends on it; weekly-digest
is updated in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
af8f4a2 to
2fa0ef9
Compare
…t style
Switches workforce's integration clients from direct REST calls to the
Relayfile-VFS writeback pattern used by sage + the cloud workflows.
Handler-side surface (ctx.github.upsertIssue, ctx.linear.comment, etc.)
stays identical; the wire underneath flips from "speak HTTP to GitHub"
to "write a JSON draft inside the Relayfile mount and let the writeback
worker do the actual API call." Aligns workforce with the rest of the
org's integration story and inherits writeback durability + retry for
free.
Substrate
- packages/runtime/src/errors.ts (top-level): WorkforceIntegrationError
moves here with the { provider, operation, cause, retryable } shape
sage/cloud already use. Old clients/errors.ts is removed; the public
surface re-exports it from the same package import path so existing
consumers (mcp-workforce) keep compiling.
- packages/runtime/src/clients/request.ts: shared VFS helpers
(readJsonFile, readTextFile, listJsonFiles, listDirectoryEntries,
writeJsonFile + atomic write-then-rename) with mount-root path
validation and optional writeback-receipt polling.
Clients
- github.ts is rewritten as a VFS client. Same GithubClient interface
(comment, createIssue, upsertIssue, getPr, postReview); each method
now reads/writes files at canonical paths under
`/github/repos/<owner>/<repo>/...`.
- linear, slack, notion, jira ship as new typed clients with the same
pattern. IntegrationClients in types.ts now types all five concretely
instead of leaving four as unknown.
Tests
- github.test.ts is rewritten end-to-end against a tempdir mount.
- linear/slack/notion/jira tests run against tempdir mounts too.
- 29 runtime tests pass (up from 18), 386 across the repo.
Example
- weekly-digest/agent.ts drops the WORKFORCE_INTEGRATION_GITHUB_TOKEN
plumbing; the github client picks up RELAYFILE_MOUNT_ROOT instead.
- weekly-digest/README.md documents the writeback model + Relayfile
mount env requirement, and drops the GITHUB_TOKEN setup step.
Notes
- mcp-workforce (PR #91) imports createGithubClient with a different
construction shape today (`{ token }`); it'll need a follow-up
commit to switch to IntegrationClientOptions once this lands. The
MCP package depends on the new shape, not the old.
- The direct-REST github implementation that shipped in #90 is
replaced wholesale. No persona today depends on it; weekly-digest
is updated in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2fa0ef9 to
8b2ebe1
Compare
…t style (#92) * feat(runtime): adopt Relayfile-VFS as the canonical integration-client style Switches workforce's integration clients from direct REST calls to the Relayfile-VFS writeback pattern used by sage + the cloud workflows. Handler-side surface (ctx.github.upsertIssue, ctx.linear.comment, etc.) stays identical; the wire underneath flips from "speak HTTP to GitHub" to "write a JSON draft inside the Relayfile mount and let the writeback worker do the actual API call." Aligns workforce with the rest of the org's integration story and inherits writeback durability + retry for free. Substrate - packages/runtime/src/errors.ts (top-level): WorkforceIntegrationError moves here with the { provider, operation, cause, retryable } shape sage/cloud already use. Old clients/errors.ts is removed; the public surface re-exports it from the same package import path so existing consumers (mcp-workforce) keep compiling. - packages/runtime/src/clients/request.ts: shared VFS helpers (readJsonFile, readTextFile, listJsonFiles, listDirectoryEntries, writeJsonFile + atomic write-then-rename) with mount-root path validation and optional writeback-receipt polling. Clients - github.ts is rewritten as a VFS client. Same GithubClient interface (comment, createIssue, upsertIssue, getPr, postReview); each method now reads/writes files at canonical paths under `/github/repos/<owner>/<repo>/...`. - linear, slack, notion, jira ship as new typed clients with the same pattern. IntegrationClients in types.ts now types all five concretely instead of leaving four as unknown. Tests - github.test.ts is rewritten end-to-end against a tempdir mount. - linear/slack/notion/jira tests run against tempdir mounts too. - 29 runtime tests pass (up from 18), 386 across the repo. Example - weekly-digest/agent.ts drops the WORKFORCE_INTEGRATION_GITHUB_TOKEN plumbing; the github client picks up RELAYFILE_MOUNT_ROOT instead. - weekly-digest/README.md documents the writeback model + Relayfile mount env requirement, and drops the GITHUB_TOKEN setup step. Notes - mcp-workforce (PR #91) imports createGithubClient with a different construction shape today (`{ token }`); it'll need a follow-up commit to switch to IntegrationClientOptions once this lands. The MCP package depends on the new shape, not the old. - The direct-REST github implementation that shipped in #90 is replaced wholesale. No persona today depends on it; weekly-digest is updated in this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * (rebase PR #92 onto post-Track-D main) Track E1: Track E1 — rebase #92 (feat/integrations-vfs) See workforce/docs/plans/deploy-v1-schema-cascade-spec.md * fix(review): address CodeRabbit comments on PR #92 - github.upsertIssue update: preserve number/state/html_url/url so the next call still finds the canonical issue file by number. - github.getPr: use the discovered pull directory segment verbatim instead of re-encoding it (avoids double-escaping slug paths like `123__fix%2Fci`). - request.waitForReceipt: short-circuit in fire-and-forget mode (timeoutMs <= 0) before reading the just-written draft, so a draft payload carrying top-level id/path/created is never reinterpreted as a writeback receipt. - jira.transition: validate that the transition id is non-empty after trimming, throwing a non-retryable WorkforceIntegrationError. - notion.createPage: throw WorkforceIntegrationError instead of a generic Error when parent.database_id is missing, matching the rest of the integration error contract. - weekly-digest README: move RELAYFILE_MOUNT_ROOT in front of `node` (was scoped only to `echo`) and add a prerequisite note that real GitHub writes require the Relayfile writeback worker to be running. * feat(examples): add review-agent + linear-shipper (Relayfile-VFS clients) (#93) * feat(examples): add review-agent + linear-shipper examples (VFS clients) Ports the two example agents from the closed codex/deploy-v1-pr branch to the Relayfile-VFS integration-client style introduced in #92. review-agent - GitHub PR opened: pulls the diff via ctx.github.getPr, runs the persona's harness on the diff body, posts a review via ctx.github.postReview. - @mention in an issue/review comment: harness with the comment thread as context, posts the reply via ctx.github.comment. - check_run.completed (failure): harness with the failed CI logs as context, proposes a fix in a comment. - Slack app_mention: conversational reply via ctx.slack. linear-shipper - Linear issue created: clones the target repo into the sandbox, runs ctx.harness.run on the issue body, opens a draft PR via ctx.github, comments back on the Linear issue with the PR link. - Headless (no traits in the persona); demonstrates the paraglide "Linear issue → ship" pattern. Both examples adapt to the WorkforceProviderEvent shape — they read the raw provider payload from event.payload rather than treating the event as the payload itself. Tests: typecheck clean across the workspace and against examples/tsconfig.json (which path-maps @agentworkforce/runtime to the workspace source). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(examples/linear-shipper): read event.payload, not event itself Same shape mismatch I fixed in review-agent: the agent was reading event.issue as if event were the raw Linear webhook body, but WorkforceProviderEvent.payload is where the provider payload lives. Without this fix, every linear.issue.created delivery to the shipper failed at the "Linear event is missing an issue id" guard because issueRef was always undefined. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(examples/linear-shipper): use a valid PERSONA_INTENT persona-kit's parser rejects unknown intents. "implementation" is in PERSONA_TAGS, not PERSONA_INTENTS, so the persona failed at parsePersonaSpec(...) with `persona[implementation].intent is invalid` before deploy could do anything. Swap to `implement-frontend` — the closest valid intent. Not a perfect domain match (the shipper isn't frontend-specific) but accurate enough to demonstrate the pattern; users will customize per their own routing taxonomy. Verified end-to-end: `workforce deploy ./examples/linear-shipper/persona.json --dry-run` now exits 0 with "persona linear-shipper: 2 integration(s)". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(persona-kit): reject removed deploy v1 persona keys * (rebase PR #93 — strip traits/sandbox from examples) Track E2: Track E2 — rebase #93 (feat/integrations-vfs-examples) See workforce/docs/plans/deploy-v1-schema-cascade-spec.md * fix(examples/linear-shipper): honor env-var overrides in inputDefault inputDefault previously only returned the static persona JSON default, silently ignoring REPO_URL / GITHUB_OWNER / GITHUB_REPO env vars that the README instructs users to set. Mirror the precedence in resolvePersonaInputs (packages/persona-kit/src/inputs.ts): env var (spec.env ?? name) wins over spec.default. Addresses devin-ai-integration review comment on PR #93. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Ricky Schema Cascade <ricky@agent-relay.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Ricky Schema Cascade <ricky@agent-relay.com>
# Conflicts: # examples/weekly-digest/README.md # packages/runtime/src/clients/github.test.ts # packages/runtime/src/clients/github.ts # packages/runtime/src/clients/jira.ts # packages/runtime/src/clients/notion.ts # packages/runtime/src/clients/request.ts # packages/runtime/src/types.ts # pnpm-lock.yaml
mcp-workforce was landed in #91 against an older `PersonaMemoryScope` shape (`session | user | workspace | org | object`). #94 then tightened the type to `workspace | user | global`. Both PRs passed CI independently, but main is now broken at build time because the zod enum in `server.ts` and the runtime `VALID_SCOPES` Set in `tools/memory.ts` still reference the removed literals. Aligning both call sites to the canonical persona-kit shape: - `MEMORY_SCOPE_ENUM` → z.enum(['workspace', 'user', 'global']) - `VALID_SCOPES` → new Set(['workspace', 'user', 'global']) - memory.save tool description updated to match The default scope stays `workspace`. Callers that previously passed `'session'`/`'org'`/`'object'` will now get a validation error from the zod schema before the runtime check — preferable to silently mapping them to a different scope. Verified: `pnpm -F @agentworkforce/mcp-workforce typecheck` + `build` + `test` (23/23) all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…p-workforce build (#104) * chore(publish): add runtime + deploy + mcp-workforce to the publish allow-list The publish.yml allow-list was last updated when the workspace had 5 packages (persona-kit, workload-router, cli, daytona-runner, agentworkforce). The deploy-v1 cascade shipped 3 more under @agentworkforce/* that the existing cli already depends on: - @agentworkforce/runtime (consumed by deploy, mcp-workforce) - @agentworkforce/deploy (consumed by cli) - @agentworkforce/mcp-workforce (consumed by harness CLIs via MCP) cli@3.0.1 already declares `@agentworkforce/deploy@0.0.0` as a runtime dep, but deploy was never published — the 0.0.0 on npm is a placeholder, so `npm i agentworkforce` today pulls a stub for `workforce deploy`. The same applies to deploy/mcp-workforce's runtime dep. This change preserves lockstep umbrella semantics and orders the publish in topological order (runtime before deploy/mcp-workforce, deploy before cli, cli before agentworkforce). personas-core stays on publish-personas.yml as before — not added here. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(mcp-workforce): align memory scope enum with PersonaMemoryScope mcp-workforce was landed in #91 against an older `PersonaMemoryScope` shape (`session | user | workspace | org | object`). #94 then tightened the type to `workspace | user | global`. Both PRs passed CI independently, but main is now broken at build time because the zod enum in `server.ts` and the runtime `VALID_SCOPES` Set in `tools/memory.ts` still reference the removed literals. Aligning both call sites to the canonical persona-kit shape: - `MEMORY_SCOPE_ENUM` → z.enum(['workspace', 'user', 'global']) - `VALID_SCOPES` → new Set(['workspace', 'user', 'global']) - memory.save tool description updated to match The default scope stays `workspace`. Callers that previously passed `'session'`/`'org'`/`'object'` will now get a validation error from the zod schema before the runtime check — preferable to silently mapping them to a different scope. Verified: `pnpm -F @agentworkforce/mcp-workforce typecheck` + `build` + `test` (23/23) all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(publish): sync release-notes packageOrder with expanded allow-list CodeRabbit + Devin both flagged that the `packageOrder` array used to sort release-note entries was not updated alongside the publish allow-list, so `runtime` / `deploy` / `mcp-workforce` would have `indexOf === -1` and sort first (or in an arbitrary order depending on the sort impl). Mirror the topological order from "Resolve target packages": persona-kit → runtime → workload-router → deploy → mcp-workforce → daytona-runner → cli → agentworkforce Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(examples): linear-shipper read spec metadata from inputSpecs `ctx.persona.inputs` is `Record<string, string>` (resolved values) on WorkforcePersonaContext; the raw `PersonaInputSpec` with `.env` and `.default` lives at `ctx.persona.inputSpecs`. The earlier env-var- precedence patch on PR #93 read .env / .default off `.inputs`, which only worked while the type was loose. The post-cascade readonly tightening exposed the bug at typecheck time and broke the examples typecheck job. Also fold the runtime-resolved value into the fallback chain so we prefer env > resolved > spec.default — matching `resolvePersonaInputs`. Verified: `pnpm run typecheck` + `pnpm run typecheck:examples` both clean after rebuilding @agentworkforce/deploy dist. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(persona-kit): make integration-source fixtures pass the schema test `emit-schema.test` failed on main because PR #97 (IntegrationConfig.source discriminator) added three new fixtures but they were missing two schema-required fields (`onEvent` for cloud personas, `skills`) and the test's hardcoded expected-filename list wasn't updated. Three small fixes: 1. `emit-schema.test.ts`: add the three new fixture names to the expected-filenames deepEqual. 2. `integration-source-{deployer,workspace,service-account}.json`: add `"onEvent": "./agent.ts"` and `"skills": []` to each, matching the pattern used in `full.json` / `cron-only.json`. The fixtures still exercise their intended IntegrationSource shapes (no-source default-inject, explicit `workspace`, explicit `workspace_service_account`) — only the cross-cutting required-for-cloud fields were added. Verified: `pnpm -F @agentworkforce/persona-kit test` → 162/162 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Ricky Schema Cascade <ricky@agent-relay.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Track C of the workforce-deploy-v1 cross-repo plan (see
workforce/docs/plans/deploy-v1-workflow-spec.md). Adds@agentworkforce/mcp-workforce— an MCP server that exposes workforce primitives (workflows, memory, integration clients) to a harness running inside a workforce sandbox via stdio MCP.Stacks on top of #90 (
feat/deploy-v1-core) — depends on@agentworkforce/runtime/clientsfor the GithubClient. Will rebase onmainonce #90 merges.Tools shipped
workflow.run/workflow.status${WORKFORCE_CLOUD_URL}/api/v1/workspaces/:id/workflows/...with the runtime-injected workspace tokenmemory.save/memory.recall/v3/memories,/v3/search) under aworkforce:<workspaceId>container tagintegration.github.{comment, createIssue, upsertIssue, getPr, postReview}@agentworkforce/runtime/clientsGithubClient, lazy-constructed fromWORKFORCE_INTEGRATION_GITHUB_TOKENDesign notes
@modelcontextprotocol/sdk(>=1.21.0).npx @agentworkforce/mcp-workforceboots the server and speaks MCP over stdin/stdout.workforce:<workspaceId>) keeps memories written here visible to sage and other agent-assistant consumers using the same shape.loadConfigenforcesWORKFORCE_WORKSPACE_IDat startup; per-tool deps (workspace token, supermemory key, provider tokens) check at first call so partial wiring is debuggable rather than fatal at boot.Tests
config.test.ts— env normalization, provider-token regex, cloud-url trimming, missing-workspace errortools/workflow.test.ts— POST/GET shape, auth header, 4xx surface, missing token errortools/memory.test.ts— supermemory POST/search, tag dedupe + workspace/scope injection, scope validation, limit-range guardtools/integrations.test.ts— tool-name parsing, unknown-provider rejection, missing-token error, real GithubClient round-trip via globalThis.fetch override, postReview enum validationserver.test.ts— full tool-set registration matches the documented surfaceTest plan
pnpm -r buildgreenpnpm --filter @agentworkforce/mcp-workforce run test— 21/21 passpnpm run typecheck(andtypecheck:examples) cleannpx @agentworkforce/mcp-workforceboots with required env, exits cleanly on stdin closememory.save+memory.recallround-trip against staging Supermemory (deferred — gated onSUPERMEMORY_API_KEYin CI)🤖 Generated with Claude Code