claude-tempo 0.28.0 → 0.29.1
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/CLAUDE.md +2 -0
- package/dashboard/package.json +1 -1
- package/dist/activities/outbox.d.ts +8 -0
- package/dist/activities/outbox.js +5 -4
- package/dist/server-tools.js +12 -0
- package/dist/tools/coat-check-evict.d.ts +4 -0
- package/dist/tools/coat-check-evict.js +43 -0
- package/dist/tools/coat-check-get.d.ts +4 -0
- package/dist/tools/coat-check-get.js +56 -0
- package/dist/tools/coat-check-list.d.ts +4 -0
- package/dist/tools/coat-check-list.js +60 -0
- package/dist/tools/coat-check-put.d.ts +4 -0
- package/dist/tools/coat-check-put.js +53 -0
- package/dist/tools/cue.js +4 -2
- package/dist/tools/worktree.js +17 -4
- package/dist/types.d.ts +75 -0
- package/dist/utils/validation.d.ts +18 -0
- package/dist/utils/validation.js +32 -1
- package/dist/utils/worktree.d.ts +18 -1
- package/dist/utils/worktree.js +68 -3
- package/dist/workflows/maestro-signals.d.ts +72 -1
- package/dist/workflows/maestro-signals.js +30 -1
- package/dist/workflows/maestro.js +242 -6
- package/dist/workflows/session.js +7 -0
- package/dist/workflows/signals.d.ts +1 -0
- package/dist/workflows/signals.js +3 -0
- package/package.json +1 -1
- package/workflow-bundle.js +315 -9
package/CLAUDE.md
CHANGED
|
@@ -91,6 +91,7 @@ src/
|
|
|
91
91
|
│ ├── pause.ts / play.ts / shutdown.ts / restore.ts
|
|
92
92
|
│ ├── hosts.ts / set-ensemble-description.ts
|
|
93
93
|
│ ├── save-state.ts / fetch-state.ts / clear-state.ts
|
|
94
|
+
│ ├── coat-check-put.ts / coat-check-get.ts / coat-check-list.ts / coat-check-evict.ts
|
|
94
95
|
│ └── helpers.ts # Zod/MCP tool registration wrapper
|
|
95
96
|
├── tui/
|
|
96
97
|
│ ├── App.tsx / store.ts / commands.ts # TUI root, state, slash commands
|
|
@@ -170,6 +171,7 @@ daemon worker notes, `npx ts-node` dev runner).
|
|
|
170
171
|
- **Claude Code headless adapter** (`agent: 'claude-code-headless'`, #520): Headless adapter that drives sessions via the official `claude` CLI as a per-turn `claude -p --output-format stream-json` subprocess. The whole point: turns bill against the host's existing Claude Code subscription extra-usage credits (Pro / Max plans) rather than a Console workspace API key — the only ToS-clean way for a third-party tool to tap that pool. Requires the `claude` binary on PATH AND a logged-in Claude Code session (`claude auth login`); recruit pre-flight rejects with an actionable error otherwise. Tool surface is the union of full Claude Code built-ins (Bash / Read / Write / Edit / Glob / Grep / WebSearch / WebFetch) and the claude-tempo MCP surface — registered via inline `--mcp-config` so `claude` spawns `dist/server.js` as its own MCP child (no in-process bridge). Recruit knobs: `permissionMode` (default `'acceptEdits'`) or `dangerouslySkipPermissions: true` (mutually exclusive). Sessions resume across restart via the existing `sessionId` metadata field — the same UUID is shared with the interactive `claude-code` adapter (per-cwd JSONL is per-cwd, not per-adapter). See `src/adapters/claude-code-headless/` and `examples/ensembles/tempo-headless-jam.yaml`.
|
|
171
172
|
- **Mock adapter** (`agent: 'mock'`, dev mode only): Four modes: `echo` (echoes input), `scripted` (replays YAML scenario rules), `silent` (drains messages without replying — heartbeat-stale validation), `chaos` (probabilistic fail/crash injection via seeded PRNG). Only registered when `isDevMode()` is true; stripped from the npm tarball by `prepack`. See `src/adapters/mock/`.
|
|
172
173
|
- **Saveable state** (#334, ADR 0011): Per-player curated state slots — the player itself decides what context survives a restart. Three MCP tools: `save_state` (owner-only write, max 4 slots × 32 KiB), `fetch_state` (read self or peer; audit identity recorded on each entry's `savedBy`), `clear_state` (owner-only). `restart` accepts `loadFromState: true | 'someKey'` to seed the new session from a saved-state slot instead of (or, with `transcript: 'replay'`, alongside) transcript replay. Saved-state delivery uses `from: 'self-restart'` as a stable system identity. Empty-slot fallback: graceful — falls through to transcript replay with a log line. See [docs/design/334-player-saveable-state.md](docs/design/334-player-saveable-state.md).
|
|
174
|
+
- **Coat-check** (#318, ADR 0008): Per-ensemble transient content store on Maestro state. Solves the 100 KB cue body cap — stash a large artifact with `coat_check_put` (returns a ticket id) and attach the ticket to a `cue` via `attachmentTicket`; the recipient calls `coat_check_get` to pull the full body. Four MCP tools: `coat_check_put` (any player; max 32 KiB per entry, 20 slots per ensemble, TTL 7d default), `coat_check_get` (any player; bumps fetch-audit counters), `coat_check_list` (read-only survey; headers only, content omitted), `coat_check_evict` (owner or conductor). Saturation rejects with `CoatCheckSlotsFull` (no LRU eviction). See `src/tools/coat-check-*.ts` and [docs/adr/0008-coat-check-pattern.md](docs/adr/0008-coat-check-pattern.md).
|
|
173
175
|
- **Lineup examples**: Six pre-built ensemble YAML files in `examples/ensembles/` — `tempo-big-band`, `tempo-dev-team`, `tempo-review-squad`, `tempo-jam-session`, `tempo-mock-jam` (dev-mode all-mock ensemble), `tempo-headless-jam` (#520 — all-`claude-code-headless` subscription-billed ensemble). Load with `claude-tempo up --lineup <name>` or the `load_lineup` tool.
|
|
174
176
|
- **GitHub App identity** (`claude-tempo[bot]`): When a player writes to GitHub — issue comments, PR creation/merge, commits, labels, check runs — **use `./scripts/ensemble-gh`** instead of `gh`. The wrapper mints a short-lived installation token so the action is attributed to `claude-tempo[bot]`, not to the human maintainer, making the AI authorship visible. Plain `gh` is still correct for read-only local dev (`gh pr view`, `gh repo clone`, `gh auth status`). Every bot-authored comment/PR body must include the AI attribution footer documented in [docs/github-app.md](docs/github-app.md).
|
|
175
177
|
|
package/dashboard/package.json
CHANGED
|
@@ -15,6 +15,14 @@ export interface DeliverCueInput {
|
|
|
15
15
|
* later TUI grouping. `undefined` for non-broadcast direct cues.
|
|
16
16
|
*/
|
|
17
17
|
broadcastId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* #318: Coat-check ticket referencing content stashed via
|
|
20
|
+
* `coat_check_put`. Threaded through to the target's `receiveMessage`
|
|
21
|
+
* signal so the stored `Message` carries it and the recipient can
|
|
22
|
+
* pull the body via `coat_check_get`. `undefined` for cues without an
|
|
23
|
+
* attachment.
|
|
24
|
+
*/
|
|
25
|
+
attachmentTicket?: string;
|
|
18
26
|
}
|
|
19
27
|
export interface DeliverReportInput {
|
|
20
28
|
ensemble: string;
|
|
@@ -140,19 +140,20 @@ function classifyAndRethrow(err, contextPrefix) {
|
|
|
140
140
|
function createOutboxActivities(client, config) {
|
|
141
141
|
return {
|
|
142
142
|
async deliverCue(input) {
|
|
143
|
-
const { ensemble, fromPlayerId, targetPlayerId, message, broadcastId } = input;
|
|
143
|
+
const { ensemble, fromPlayerId, targetPlayerId, message, broadcastId, attachmentTicket } = input;
|
|
144
144
|
try {
|
|
145
145
|
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
146
146
|
if (!handle) {
|
|
147
147
|
throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
|
|
148
148
|
}
|
|
149
|
-
// #357: thread broadcastId
|
|
150
|
-
// `receiveMessage` signal payload.
|
|
151
|
-
// direct cues omit
|
|
149
|
+
// #357 + #318: thread broadcastId / attachmentTicket onto the
|
|
150
|
+
// receiver's `receiveMessage` signal payload. Both fields are
|
|
151
|
+
// additive optionals — direct cues omit one or both.
|
|
152
152
|
await handle.signal('receiveMessage', {
|
|
153
153
|
from: fromPlayerId,
|
|
154
154
|
text: message,
|
|
155
155
|
...(broadcastId !== undefined ? { broadcastId } : {}),
|
|
156
|
+
...(attachmentTicket !== undefined ? { attachmentTicket } : {}),
|
|
156
157
|
});
|
|
157
158
|
return { success: true };
|
|
158
159
|
}
|
package/dist/server-tools.js
CHANGED
|
@@ -40,6 +40,11 @@ const set_ensemble_description_1 = require("./tools/set-ensemble-description");
|
|
|
40
40
|
const save_state_1 = require("./tools/save-state");
|
|
41
41
|
const fetch_state_1 = require("./tools/fetch-state");
|
|
42
42
|
const clear_state_1 = require("./tools/clear-state");
|
|
43
|
+
// #318 — ensemble-shared coat-check (put / get / list / evict).
|
|
44
|
+
const coat_check_put_1 = require("./tools/coat-check-put");
|
|
45
|
+
const coat_check_get_1 = require("./tools/coat-check-get");
|
|
46
|
+
const coat_check_list_1 = require("./tools/coat-check-list");
|
|
47
|
+
const coat_check_evict_1 = require("./tools/coat-check-evict");
|
|
43
48
|
/**
|
|
44
49
|
* Register every tempo MCP tool onto `server`. Single source of truth — the
|
|
45
50
|
* stdio MCP server (`src/server.ts`) and the in-process MCP server in the
|
|
@@ -83,6 +88,13 @@ function registerAllTempoTools(server, opts) {
|
|
|
83
88
|
(0, save_state_1.registerSaveStateTool)(server, handle, getPlayerId);
|
|
84
89
|
(0, fetch_state_1.registerFetchStateTool)(server, client, config, handle, getPlayerId);
|
|
85
90
|
(0, clear_state_1.registerClearStateTool)(server, handle);
|
|
91
|
+
// #318 — ensemble-shared coat-check (put/get/list/evict). Any player can put;
|
|
92
|
+
// any player can get/list; owner-or-conductor can evict. Audit identity is
|
|
93
|
+
// set at the tool layer via getPlayerId() — no playerId arg on any schema.
|
|
94
|
+
(0, coat_check_put_1.registerCoatCheckPutTool)(server, client, config, getPlayerId);
|
|
95
|
+
(0, coat_check_get_1.registerCoatCheckGetTool)(server, client, config, getPlayerId);
|
|
96
|
+
(0, coat_check_list_1.registerCoatCheckListTool)(server, client, config);
|
|
97
|
+
(0, coat_check_evict_1.registerCoatCheckEvictTool)(server, client, config, getPlayerId);
|
|
86
98
|
if (isConductor) {
|
|
87
99
|
(0, quality_gate_1.registerQualityGateTool)(server, handle, getPlayerId);
|
|
88
100
|
(0, evaluate_gate_1.registerEvaluateGateTool)(server, handle, getPlayerId);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { Client } from '@temporalio/client';
|
|
3
|
+
import { Config } from '../config';
|
|
4
|
+
export declare function registerCoatCheckEvictTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCoatCheckEvictTool = registerCoatCheckEvictTool;
|
|
4
|
+
/**
|
|
5
|
+
* `coat_check_evict` — remove a coat-check entry (#318, ADR 0008) before
|
|
6
|
+
* its TTL expires. Owner-or-conductor only: the workflow validator rejects
|
|
7
|
+
* mismatched `evictedBy` with `CoatCheckEvictPermissionDenied`. Returns
|
|
8
|
+
* `evicted: false` when the ticket was already missing / expired before
|
|
9
|
+
* the call landed.
|
|
10
|
+
*
|
|
11
|
+
* Audit identity (`evictedBy`) is set by the tool layer from
|
|
12
|
+
* `getPlayerId()` — there is NO `evictedBy` arg on the MCP schema.
|
|
13
|
+
*/
|
|
14
|
+
const zod_1 = require("zod");
|
|
15
|
+
const config_1 = require("../config");
|
|
16
|
+
const maestro_signals_1 = require("../workflows/maestro-signals");
|
|
17
|
+
const helpers_1 = require("./helpers");
|
|
18
|
+
const validation_1 = require("../utils/validation");
|
|
19
|
+
function registerCoatCheckEvictTool(server, client, config, getPlayerId) {
|
|
20
|
+
(0, helpers_1.defineTool)(server, 'coat_check_evict', `Evict a coat-check entry (#318) before its TTL expires. Owner-or-conductor only — non-owners (and non-conductors) get a permission error.
|
|
21
|
+
|
|
22
|
+
Use to free a slot when this ensemble is at the 20-entry cap and you want to make room. \`evicted: false\` means the ticket was already gone (TTL-expired or evicted by someone else).`, {
|
|
23
|
+
ticket: zod_1.z.string().regex(validation_1.COAT_CHECK_TICKET_REGEX).max(validation_1.COAT_CHECK_TICKET_MAX).describe(`The ticket id returned by an earlier \`coat_check_put\` (≤${validation_1.COAT_CHECK_TICKET_MAX} chars).`),
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const { ticket } = args;
|
|
26
|
+
const evictedBy = getPlayerId();
|
|
27
|
+
try {
|
|
28
|
+
const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
|
|
29
|
+
const result = await handle.executeUpdate(maestro_signals_1.coatCheckEvictUpdate, {
|
|
30
|
+
args: [{ ticket, evictedBy }],
|
|
31
|
+
});
|
|
32
|
+
if (!result.evicted) {
|
|
33
|
+
return (0, helpers_1.ok)(`Ticket **${ticket}** was already gone (no-op).`);
|
|
34
|
+
}
|
|
35
|
+
return (0, helpers_1.ok)(`Evicted ticket **${ticket}**.`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
// Surfaces `CoatCheckEvictPermissionDenied` ApplicationFailure with
|
|
39
|
+
// owner/conductor diagnostic from the workflow validator.
|
|
40
|
+
return (0, helpers_1.fail)(`Failed to evict ticket: ${(0, helpers_1.formatError)(err)}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { Client } from '@temporalio/client';
|
|
3
|
+
import { Config } from '../config';
|
|
4
|
+
export declare function registerCoatCheckGetTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCoatCheckGetTool = registerCoatCheckGetTool;
|
|
4
|
+
/**
|
|
5
|
+
* `coat_check_get` — redeem a coat-check ticket and pull the stashed content
|
|
6
|
+
* (#318, ADR 0008). Returns the entry's summary, content body, and audit
|
|
7
|
+
* fields, or `null` when the ticket is missing / expired / evicted (no
|
|
8
|
+
* error-class proliferation for the common "ticket already gone" case).
|
|
9
|
+
*
|
|
10
|
+
* Audit identity (`fetchedBy`) is set by the tool layer from `getPlayerId()`
|
|
11
|
+
* — there is NO `fetchedBy` arg on the MCP schema. The workflow handler
|
|
12
|
+
* bumps `lastFetchedAt` / `lastFetchedBy` / `fetchCount` on the entry so
|
|
13
|
+
* the putter can later see whether anyone has redeemed.
|
|
14
|
+
*
|
|
15
|
+
* Implemented as a workflow Update (not Query) because the fetch-audit
|
|
16
|
+
* counters mutate state — Queries cannot mutate.
|
|
17
|
+
*/
|
|
18
|
+
const zod_1 = require("zod");
|
|
19
|
+
const config_1 = require("../config");
|
|
20
|
+
const maestro_signals_1 = require("../workflows/maestro-signals");
|
|
21
|
+
const helpers_1 = require("./helpers");
|
|
22
|
+
const validation_1 = require("../utils/validation");
|
|
23
|
+
function registerCoatCheckGetTool(server, client, config, getPlayerId) {
|
|
24
|
+
(0, helpers_1.defineTool)(server, 'coat_check_get', `Redeem a coat-check ticket (#318) and pull the stashed content. Returns the entry's summary, content body, and audit info — or "not found" when the ticket is missing / expired / evicted (no error, just empty).
|
|
25
|
+
|
|
26
|
+
Successful redemptions bump the entry's fetch-audit counters (\`lastFetchedAt\` / \`lastFetchedBy\` / \`fetchCount\`) so the putter can later see whether anyone has redeemed. \`coat_check_list\` won't bump these — only an actual redemption counts.`, {
|
|
27
|
+
ticket: zod_1.z.string().regex(validation_1.COAT_CHECK_TICKET_REGEX).max(validation_1.COAT_CHECK_TICKET_MAX).describe(`The ticket id returned by an earlier \`coat_check_put\` (≤${validation_1.COAT_CHECK_TICKET_MAX} chars).`),
|
|
28
|
+
}, async (args) => {
|
|
29
|
+
const { ticket } = args;
|
|
30
|
+
const fetchedBy = getPlayerId();
|
|
31
|
+
try {
|
|
32
|
+
const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
|
|
33
|
+
const entry = await handle.executeUpdate(maestro_signals_1.coatCheckGetUpdate, {
|
|
34
|
+
args: [{ ticket, fetchedBy }],
|
|
35
|
+
});
|
|
36
|
+
if (!entry) {
|
|
37
|
+
return (0, helpers_1.ok)(`Ticket **${ticket}** is not found (missing, expired, or evicted).`);
|
|
38
|
+
}
|
|
39
|
+
const lines = [
|
|
40
|
+
`**Ticket ${ticket}** — stashed by ${entry.putBy} at ${entry.putAt}, expires ${entry.expiresAt}`,
|
|
41
|
+
`Summary: ${entry.summary}`,
|
|
42
|
+
];
|
|
43
|
+
if (entry.contentType)
|
|
44
|
+
lines.push(`Content-Type: ${entry.contentType}`);
|
|
45
|
+
lines.push(`Size: ${entry.size} bytes`);
|
|
46
|
+
lines.push(`Fetches: ${entry.fetchCount}${entry.lastFetchedBy ? ` (last by ${entry.lastFetchedBy} at ${entry.lastFetchedAt})` : ''}`);
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push('---');
|
|
49
|
+
lines.push(entry.content);
|
|
50
|
+
return (0, helpers_1.ok)(lines.join('\n'));
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return (0, helpers_1.fail)(`Failed to redeem ticket: ${(0, helpers_1.formatError)(err)}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCoatCheckListTool = registerCoatCheckListTool;
|
|
4
|
+
/**
|
|
5
|
+
* `coat_check_list` — list coat-check entry headers (#318, ADR 0008) for
|
|
6
|
+
* the calling player's ensemble. Read-only — does NOT bump fetch-audit
|
|
7
|
+
* counters (only an actual `coat_check_get` redemption counts).
|
|
8
|
+
*
|
|
9
|
+
* Returns headers (no `content` body) sorted newest-first. Optional
|
|
10
|
+
* `putBy` / `prefix` / `unfetchedOnly` filters narrow the result.
|
|
11
|
+
*/
|
|
12
|
+
const zod_1 = require("zod");
|
|
13
|
+
const config_1 = require("../config");
|
|
14
|
+
const maestro_signals_1 = require("../workflows/maestro-signals");
|
|
15
|
+
const helpers_1 = require("./helpers");
|
|
16
|
+
const validation_1 = require("../utils/validation");
|
|
17
|
+
function registerCoatCheckListTool(server, client, config) {
|
|
18
|
+
(0, helpers_1.defineTool)(server, 'coat_check_list', `List coat-check entries (#318) for this ensemble. Read-only — does NOT bump fetch-audit counters; only \`coat_check_get\` does. Sorted newest-first. Pass \`unfetchedOnly: true\` to surface entries nobody has redeemed yet — useful for an owner cleaning up stale stashes.`, {
|
|
19
|
+
putBy: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Optional filter — only entries stashed by this player.'),
|
|
20
|
+
prefix: zod_1.z.string().max(validation_1.COAT_CHECK_SUMMARY_MAX).optional().describe('Optional summary-prefix filter — narrows the listing to entries whose summary starts with this string.'),
|
|
21
|
+
unfetchedOnly: zod_1.z.boolean().optional().describe('When true, only return entries with fetchCount=0 (never redeemed). Default false.'),
|
|
22
|
+
}, async (args) => {
|
|
23
|
+
const { putBy, prefix, unfetchedOnly } = args;
|
|
24
|
+
try {
|
|
25
|
+
const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
|
|
26
|
+
const filter = {
|
|
27
|
+
...(putBy !== undefined ? { putBy } : {}),
|
|
28
|
+
...(prefix !== undefined ? { prefix } : {}),
|
|
29
|
+
...(unfetchedOnly !== undefined ? { unfetchedOnly } : {}),
|
|
30
|
+
};
|
|
31
|
+
const headers = await handle.query(maestro_signals_1.coatCheckListQuery, filter);
|
|
32
|
+
if (headers.length === 0) {
|
|
33
|
+
const filters = [];
|
|
34
|
+
if (putBy)
|
|
35
|
+
filters.push(`putBy="${putBy}"`);
|
|
36
|
+
if (prefix)
|
|
37
|
+
filters.push(`prefix="${prefix}"`);
|
|
38
|
+
if (unfetchedOnly)
|
|
39
|
+
filters.push('unfetchedOnly=true');
|
|
40
|
+
const suffix = filters.length > 0 ? ` (filter: ${filters.join(', ')})` : '';
|
|
41
|
+
return (0, helpers_1.ok)(`No coat-check entries in this ensemble${suffix}.`);
|
|
42
|
+
}
|
|
43
|
+
const lines = [];
|
|
44
|
+
lines.push(`${headers.length}/${validation_1.COAT_CHECK_SLOTS_MAX} coat-check ${headers.length === 1 ? 'entry' : 'entries'}:`);
|
|
45
|
+
lines.push('');
|
|
46
|
+
for (const h of headers) {
|
|
47
|
+
const fetchTag = h.fetchCount === 0
|
|
48
|
+
? ' (unfetched)'
|
|
49
|
+
: ` (fetched ${h.fetchCount}× by ${h.lastFetchedBy ?? '(unknown)'} at ${h.lastFetchedAt ?? '(unknown)'})`;
|
|
50
|
+
const typeTag = h.contentType ? ` [${h.contentType}]` : '';
|
|
51
|
+
lines.push(`- **${h.ticket}** ${typeTag}— ${h.summary}`);
|
|
52
|
+
lines.push(` putBy=${h.putBy} · putAt=${h.putAt} · expires=${h.expiresAt} · ${h.size} bytes${fetchTag}`);
|
|
53
|
+
}
|
|
54
|
+
return (0, helpers_1.ok)(lines.join('\n'));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return (0, helpers_1.fail)(`Failed to list coat-check entries: ${(0, helpers_1.formatError)(err)}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { Client } from '@temporalio/client';
|
|
3
|
+
import { Config } from '../config';
|
|
4
|
+
export declare function registerCoatCheckPutTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCoatCheckPutTool = registerCoatCheckPutTool;
|
|
4
|
+
/**
|
|
5
|
+
* `coat_check_put` — stash a large content body on per-ensemble Maestro state
|
|
6
|
+
* (#318, ADR 0008). Returns a ticket id that any player in the ensemble can
|
|
7
|
+
* later redeem via `coat_check_get` (or pass on a `cue`'s `attachmentTicket`
|
|
8
|
+
* field so the recipient knows what to fetch).
|
|
9
|
+
*
|
|
10
|
+
* Audit identity (`putBy`) is set by the tool layer from `getPlayerId()` —
|
|
11
|
+
* the MCP schema has NO `playerId` arg, so callers cannot spoof. Same
|
|
12
|
+
* structural-permission pattern as `save_state` (#334).
|
|
13
|
+
*/
|
|
14
|
+
const zod_1 = require("zod");
|
|
15
|
+
const config_1 = require("../config");
|
|
16
|
+
const maestro_signals_1 = require("../workflows/maestro-signals");
|
|
17
|
+
const helpers_1 = require("./helpers");
|
|
18
|
+
const validation_1 = require("../utils/validation");
|
|
19
|
+
function registerCoatCheckPutTool(server, client, config, getPlayerId) {
|
|
20
|
+
(0, helpers_1.defineTool)(server, 'coat_check_put', `Stash a large content body on this ensemble's coat-check (#318). Returns a ticket id any player can redeem later via \`coat_check_get\`. Pass the ticket on a \`cue\`'s \`attachmentTicket\` field so the recipient knows what to fetch.
|
|
21
|
+
|
|
22
|
+
Use this when your message body would otherwise exceed the cue's 100 KB cap — researcher reports, review-item dumps, etc. The cue body should carry a short summary; the coat-check entry holds the full artifact.
|
|
23
|
+
|
|
24
|
+
Limits: ${validation_1.COAT_CHECK_CONTENT_MAX} bytes (UTF-8) per entry, max ${validation_1.COAT_CHECK_SLOTS_MAX} live entries per ensemble. Saturation rejects with \`CoatCheckSlotsFull\` — wait for TTL or \`coat_check_evict\` an entry you own. TTL defaults to 7 days (configurable per put within [1h, 30d]).`, {
|
|
25
|
+
summary: zod_1.z.string().min(1).max(validation_1.COAT_CHECK_SUMMARY_MAX).describe(`Short preamble surfaced in \`coat_check_list\` and on dashboards (≤${validation_1.COAT_CHECK_SUMMARY_MAX} chars). 1-2 sentences describing what the recipient gets if they redeem.`),
|
|
26
|
+
content: zod_1.z.string().min(1).max(validation_1.COAT_CHECK_CONTENT_MAX).describe(`The full content body — markdown encouraged, opaque to the system. Max ${validation_1.COAT_CHECK_CONTENT_MAX} bytes (UTF-8).`),
|
|
27
|
+
contentType: zod_1.z.string().max(validation_1.COAT_CHECK_CONTENT_TYPE_MAX).optional().describe(`Optional MIME-shaped hint (e.g. "text/markdown"). Free-form; ≤${validation_1.COAT_CHECK_CONTENT_TYPE_MAX} chars.`),
|
|
28
|
+
ttlMs: zod_1.z.number().int().min(validation_1.COAT_CHECK_TTL_MIN_MS).max(validation_1.COAT_CHECK_TTL_MAX_MS).optional().describe(`Time-to-live in milliseconds. Default ${validation_1.COAT_CHECK_TTL_DEFAULT_MS} (7 days). Range [${validation_1.COAT_CHECK_TTL_MIN_MS}, ${validation_1.COAT_CHECK_TTL_MAX_MS}] (1h to 30d).`),
|
|
29
|
+
}, async (args) => {
|
|
30
|
+
const { summary, content, contentType, ttlMs } = args;
|
|
31
|
+
const putBy = getPlayerId();
|
|
32
|
+
try {
|
|
33
|
+
const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
|
|
34
|
+
const result = await handle.executeUpdate(maestro_signals_1.coatCheckPutUpdate, {
|
|
35
|
+
args: [{
|
|
36
|
+
summary,
|
|
37
|
+
content,
|
|
38
|
+
...(contentType !== undefined ? { contentType } : {}),
|
|
39
|
+
...(ttlMs !== undefined ? { ttlMs } : {}),
|
|
40
|
+
putBy,
|
|
41
|
+
}],
|
|
42
|
+
});
|
|
43
|
+
return (0, helpers_1.ok)(`Stashed as ticket **${result.ticket}** (expires ${result.expiresAt}). Slots: ${result.slotsUsed}/${result.slotsTotal}.`);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
// The workflow validator surfaces structured ApplicationFailure
|
|
47
|
+
// errors (`CoatCheckSlotsFull`, `CoatCheckEntryTooLarge`, …). The
|
|
48
|
+
// `formatError` message preserves the workflow text so the LLM
|
|
49
|
+
// sees the oldest-3 ticket list and can pick which to evict.
|
|
50
|
+
return (0, helpers_1.fail)(`Failed to stash content: ${(0, helpers_1.formatError)(err)}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
package/dist/tools/cue.js
CHANGED
|
@@ -56,11 +56,12 @@ function formatDetachedDeliveryError(playerId, phase) {
|
|
|
56
56
|
`(3) the workflow inbox queues the signal and auto-delivers on re-attach.`);
|
|
57
57
|
}
|
|
58
58
|
function registerCueTool(server, client, config, getPlayerId, handle) {
|
|
59
|
-
(0, helpers_1.defineTool)(server, 'cue', 'Send a message to another Claude Code session by player name. Delivered instantly via Temporal signal.', {
|
|
59
|
+
(0, helpers_1.defineTool)(server, 'cue', 'Send a message to another Claude Code session by player name. Delivered instantly via Temporal signal. For content larger than ~100 KB, use `coat_check_put` to stash the body and pass the returned ticket via `attachmentTicket` — the cue body itself should carry a short summary the recipient can act on without fetching.', {
|
|
60
60
|
playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player name of the target session'),
|
|
61
61
|
message: zod_1.z.string().max(validation_1.MESSAGE_MAX).describe('The message to send'),
|
|
62
|
+
attachmentTicket: zod_1.z.string().regex(validation_1.COAT_CHECK_TICKET_REGEX).max(validation_1.COAT_CHECK_TICKET_MAX).optional().describe('Optional coat-check ticket (#318). Reference content stashed via `coat_check_put`; the receiver sees the ticket on their `recall` message and can pull the body via `coat_check_get`. Backward-compatible — omit for normal cues.'),
|
|
62
63
|
}, async (args) => {
|
|
63
|
-
const { playerId, message } = args;
|
|
64
|
+
const { playerId, message, attachmentTicket } = args;
|
|
64
65
|
const nameError = (0, validation_1.validatePlayerName)(playerId);
|
|
65
66
|
if (nameError)
|
|
66
67
|
return (0, helpers_1.fail)(nameError);
|
|
@@ -99,6 +100,7 @@ function registerCueTool(server, client, config, getPlayerId, handle) {
|
|
|
99
100
|
type: 'cue',
|
|
100
101
|
targetPlayerId: playerId,
|
|
101
102
|
message,
|
|
103
|
+
...(attachmentTicket !== undefined ? { attachmentTicket } : {}),
|
|
102
104
|
};
|
|
103
105
|
const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
|
|
104
106
|
return (0, helpers_1.ok)(`Message sent to ${playerId}. (outbox: ${entryId})`);
|
package/dist/tools/worktree.js
CHANGED
|
@@ -41,7 +41,7 @@ const helpers_1 = require("./helpers");
|
|
|
41
41
|
const worktree_1 = require("../utils/worktree");
|
|
42
42
|
const validation_1 = require("../utils/validation");
|
|
43
43
|
function registerWorktreeTool(server, client, config, handle, getPlayerId) {
|
|
44
|
-
(0, helpers_1.defineTool)(server, 'worktree', 'Manage git worktrees for player isolation. Conductor only. Actions: create (provision worktree for a player), remove (clean up), list (show active worktrees). Use when multiple players commit to different branches of the same repo simultaneously; skip for read-only work, sequential work, or tasks under ~5 min. See docs/orchestration.md#when-to-use-worktrees.', {
|
|
44
|
+
(0, helpers_1.defineTool)(server, 'worktree', 'Manage git worktrees for player isolation. Conductor only. Actions: create (provision worktree for a player), remove (clean up), list (show active worktrees). Use when multiple players commit to different branches of the same repo simultaneously; skip for read-only work, sequential work, or tasks under ~5 min. IMPORTANT: before `remove`, have the player stop any long-running processes inside the worktree (dev servers, file watchers) — on Windows a memory-mapped native module will block directory removal and `remove` will fail. See docs/orchestration.md#when-to-use-worktrees.', {
|
|
45
45
|
action: zod_1.z.enum(['create', 'remove', 'list']).describe('Action to perform'),
|
|
46
46
|
player: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Player name (required for create/remove)'),
|
|
47
47
|
branch: zod_1.z.string().optional().describe('Git branch for the worktree (defaults to {ensemble}/{player-name})'),
|
|
@@ -132,9 +132,22 @@ function registerWorktreeTool(server, client, config, handle, getPlayerId) {
|
|
|
132
132
|
if (!entry) {
|
|
133
133
|
return (0, helpers_1.fail)(`No worktree found for player "${player}".`);
|
|
134
134
|
}
|
|
135
|
-
// Remove from disk
|
|
136
|
-
(
|
|
137
|
-
//
|
|
135
|
+
// Remove from disk. #594: removeWorktree throws if the directory
|
|
136
|
+
// survives the removal (Windows file-lock half-removal). We must
|
|
137
|
+
// NOT signal `removeWorktree` state or cue the player until disk
|
|
138
|
+
// removal is confirmed — otherwise Temporal state records "no
|
|
139
|
+
// worktree" while a locked orphan directory remains on disk, and
|
|
140
|
+
// the next `create` fails with a confusing git fatal.
|
|
141
|
+
try {
|
|
142
|
+
(0, worktree_1.removeWorktree)(entry.path, entry.gitRoot);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return (0, helpers_1.fail)(`Worktree for **${player}** could not be removed: ${(0, helpers_1.formatError)(err)}\n\n` +
|
|
146
|
+
`Conductor state is unchanged — the worktree is still tracked. ` +
|
|
147
|
+
`Have the player stop any long-running processes inside the worktree ` +
|
|
148
|
+
`(dev servers, file watchers), then retry \`worktree remove\`.`);
|
|
149
|
+
}
|
|
150
|
+
// Remove from conductor state (only reached on confirmed disk removal)
|
|
138
151
|
await handle.signal('removeWorktree', player);
|
|
139
152
|
// Auto-cue the player
|
|
140
153
|
try {
|
package/dist/types.d.ts
CHANGED
|
@@ -437,6 +437,13 @@ export interface Message {
|
|
|
437
437
|
* for non-broadcast direct cues.
|
|
438
438
|
*/
|
|
439
439
|
broadcastId?: string;
|
|
440
|
+
/**
|
|
441
|
+
* #318: Coat-check ticket id when the sender stashed content via
|
|
442
|
+
* `coat_check_put` and called `cue` with `attachmentTicket`. Receivers
|
|
443
|
+
* call `coat_check_get` with this value to pull the full content body.
|
|
444
|
+
* `undefined` for cues without an attachment.
|
|
445
|
+
*/
|
|
446
|
+
attachmentTicket?: string;
|
|
440
447
|
}
|
|
441
448
|
export interface SentMessage {
|
|
442
449
|
id: string;
|
|
@@ -485,6 +492,14 @@ export interface CueOutboxEntry extends OutboxEntryBase {
|
|
|
485
492
|
* into a single chat row. `undefined` for non-broadcast cues.
|
|
486
493
|
*/
|
|
487
494
|
broadcastId?: string;
|
|
495
|
+
/**
|
|
496
|
+
* #318: Optional coat-check ticket id referencing content stashed on
|
|
497
|
+
* per-ensemble Maestro state via `coat_check_put`. Receivers see this
|
|
498
|
+
* field on their delivered `Message` and can call `coat_check_get` to
|
|
499
|
+
* pull the full content body. Backward-compatible — pre-#318 cues just
|
|
500
|
+
* don't have it.
|
|
501
|
+
*/
|
|
502
|
+
attachmentTicket?: string;
|
|
488
503
|
}
|
|
489
504
|
export interface RecruitOutboxEntry extends OutboxEntryBase {
|
|
490
505
|
type: 'recruit';
|
|
@@ -941,5 +956,65 @@ export interface MaestroInput {
|
|
|
941
956
|
startMs: number;
|
|
942
957
|
count: number;
|
|
943
958
|
};
|
|
959
|
+
/**
|
|
960
|
+
* #318 coat-check pattern — ticket-keyed stash for large content. Carried
|
|
961
|
+
* across CAN only when the map is non-empty (mirrors the `playerState`
|
|
962
|
+
* carry idiom in `src/workflows/session.ts:1617-1638`).
|
|
963
|
+
*/
|
|
964
|
+
coatCheck?: Record<string, CoatCheckEntry>;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* A single coat-check entry as stored on per-ensemble Maestro workflow
|
|
968
|
+
* state. `content` is opaque to the system — author-supplied markdown,
|
|
969
|
+
* JSON, or arbitrary text up to `COAT_CHECK_CONTENT_MAX`. All timestamps
|
|
970
|
+
* are ISO 8601 from `workflowNow()` for replay determinism.
|
|
971
|
+
*
|
|
972
|
+
* The `lastFetched*` / `fetchCount` triple is the fetch-audit surface
|
|
973
|
+
* vinceblank added on top of the architect's verdict — owners need to
|
|
974
|
+
* know if a recipient consumed their ticket. Default semantics: undefined
|
|
975
|
+
* `lastFetchedAt` + `fetchCount === 0` means "never fetched."
|
|
976
|
+
*/
|
|
977
|
+
export interface CoatCheckEntry {
|
|
978
|
+
/** Short human-readable preamble (≤ `COAT_CHECK_SUMMARY_MAX`). */
|
|
979
|
+
summary: string;
|
|
980
|
+
/** Opaque body (≤ `COAT_CHECK_CONTENT_MAX` UTF-8 bytes). */
|
|
981
|
+
content: string;
|
|
982
|
+
/** Optional MIME-shaped hint (e.g. `'text/markdown'`); free-form. */
|
|
983
|
+
contentType?: string;
|
|
984
|
+
/** Audit identity — player who called `coat_check_put`. */
|
|
985
|
+
putBy: string;
|
|
986
|
+
/** When the entry was admitted, `workflowNow().toISOString()`. */
|
|
987
|
+
putAt: string;
|
|
988
|
+
/** When the entry expires under TTL inline-sweep, `workflowNow().toISOString()`. */
|
|
989
|
+
expiresAt: string;
|
|
990
|
+
/** UTF-8 byte length of `content` — pre-computed at put time for cheap listing. */
|
|
991
|
+
size: number;
|
|
992
|
+
/** Last successful `coat_check_get` timestamp, or undefined if never fetched. */
|
|
993
|
+
lastFetchedAt?: string;
|
|
994
|
+
/** Last `coat_check_get` caller's playerId, or undefined if never fetched. */
|
|
995
|
+
lastFetchedBy?: string;
|
|
996
|
+
/** Successful `coat_check_get` count. 0 until first fetch. */
|
|
997
|
+
fetchCount: number;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Listing projection — `coat_check_list` and the put/evict update return
|
|
1001
|
+
* shapes that omit `content` so callers can survey without pulling the
|
|
1002
|
+
* whole body for every entry. `size` is preserved so dashboards can
|
|
1003
|
+
* present an at-a-glance "how big" without an extra fetch.
|
|
1004
|
+
*/
|
|
1005
|
+
export interface CoatCheckEntryHeader {
|
|
1006
|
+
ticket: string;
|
|
1007
|
+
summary: string;
|
|
1008
|
+
contentType?: string;
|
|
1009
|
+
putBy: string;
|
|
1010
|
+
putAt: string;
|
|
1011
|
+
expiresAt: string;
|
|
1012
|
+
size: number;
|
|
1013
|
+
/** Mirrors `CoatCheckEntry.lastFetchedAt`. */
|
|
1014
|
+
lastFetchedAt?: string;
|
|
1015
|
+
/** Mirrors `CoatCheckEntry.lastFetchedBy`. */
|
|
1016
|
+
lastFetchedBy?: string;
|
|
1017
|
+
/** Mirrors `CoatCheckEntry.fetchCount`. */
|
|
1018
|
+
fetchCount: number;
|
|
944
1019
|
}
|
|
945
1020
|
export {};
|
|
@@ -28,6 +28,24 @@ export declare const PLAYER_STATE_KEY_MAX = 32;
|
|
|
28
28
|
export declare const PLAYER_STATE_CONTENT_MAX: number;
|
|
29
29
|
/** Maximum number of populated slots per player. Saturation rejects with `PlayerStateSlotsFull`. */
|
|
30
30
|
export declare const PLAYER_STATE_SLOTS_MAX = 4;
|
|
31
|
+
/** Coat-check ticket id pattern. Alphanumeric + underscore + hyphen, generated server-side via `uuid4()`. */
|
|
32
|
+
export declare const COAT_CHECK_TICKET_REGEX: RegExp;
|
|
33
|
+
/** Maximum coat-check ticket id length. Generous to accommodate uuid4 + future versioning prefixes. */
|
|
34
|
+
export declare const COAT_CHECK_TICKET_MAX = 64;
|
|
35
|
+
/** Maximum content size per coat-check entry — 32 KiB. Mirrors `PLAYER_STATE_CONTENT_MAX`. */
|
|
36
|
+
export declare const COAT_CHECK_CONTENT_MAX: number;
|
|
37
|
+
/** Maximum summary length per coat-check entry — short, listing-friendly preamble. */
|
|
38
|
+
export declare const COAT_CHECK_SUMMARY_MAX = 500;
|
|
39
|
+
/** Maximum free-form contentType string (e.g. `'text/markdown'`). */
|
|
40
|
+
export declare const COAT_CHECK_CONTENT_TYPE_MAX = 64;
|
|
41
|
+
/** Maximum populated coat-check slots per ensemble. Saturation rejects with `CoatCheckSlotsFull`. */
|
|
42
|
+
export declare const COAT_CHECK_SLOTS_MAX = 20;
|
|
43
|
+
/** Minimum allowed `ttlMs` argument on `coat_check_put` — 1 hour. */
|
|
44
|
+
export declare const COAT_CHECK_TTL_MIN_MS: number;
|
|
45
|
+
/** Maximum allowed `ttlMs` argument on `coat_check_put` — 30 days. */
|
|
46
|
+
export declare const COAT_CHECK_TTL_MAX_MS: number;
|
|
47
|
+
/** Default `ttlMs` when caller omits the argument — 7 days. */
|
|
48
|
+
export declare const COAT_CHECK_TTL_DEFAULT_MS: number;
|
|
31
49
|
/**
|
|
32
50
|
* Maximum length of the ensemble description set via
|
|
33
51
|
* `set_ensemble_description` (#399 W1, Q5.1). Soft cap — the MCP tool
|
package/dist/utils/validation.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Used by MCP tool Zod schemas and config validation.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.DEFAULT_RESTART_LEASE_MS = exports.DEFAULT_RESTART_DETACH_DEADLINE_MS = exports.MAX_DETACH_DEADLINE_MS = exports.RESTART_CONTEXT_MESSAGES_MAX = exports.PREVIEW_MAX_LENGTH = exports.WORKTREE_INSTALL_TIMEOUT = exports.GATE_NOTES_MAX = exports.GATE_CRITERION_TEXT_MAX = exports.GATE_CRITERIA_MAX = exports.GATE_TASK_MAX = exports.CRON_EXPRESSION_MAX = exports.SCHEDULE_MESSAGE_MAX = exports.SCHEDULE_NAME_MAX = exports.PATH_MAX = exports.ENSEMBLE_DESCRIPTION_MAX = exports.PLAYER_STATE_SLOTS_MAX = exports.PLAYER_STATE_CONTENT_MAX = exports.PLAYER_STATE_KEY_MAX = exports.PLAYER_STATE_KEY_REGEX = exports.PLAYER_STATE_DEFAULT_KEY = exports.PART_MAX = exports.MESSAGE_MAX = exports.STAGE_PLAYERS_MAX = exports.STAGE_NAME_MAX = exports.ENSEMBLE_NAME_REGEX = exports.PLAYER_NAME_MAX = exports.PLAYER_NAME_REGEX = void 0;
|
|
7
|
+
exports.DEFAULT_RESTART_LEASE_MS = exports.DEFAULT_RESTART_DETACH_DEADLINE_MS = exports.MAX_DETACH_DEADLINE_MS = exports.RESTART_CONTEXT_MESSAGES_MAX = exports.PREVIEW_MAX_LENGTH = exports.WORKTREE_INSTALL_TIMEOUT = exports.GATE_NOTES_MAX = exports.GATE_CRITERION_TEXT_MAX = exports.GATE_CRITERIA_MAX = exports.GATE_TASK_MAX = exports.CRON_EXPRESSION_MAX = exports.SCHEDULE_MESSAGE_MAX = exports.SCHEDULE_NAME_MAX = exports.PATH_MAX = exports.ENSEMBLE_DESCRIPTION_MAX = exports.COAT_CHECK_TTL_DEFAULT_MS = exports.COAT_CHECK_TTL_MAX_MS = exports.COAT_CHECK_TTL_MIN_MS = exports.COAT_CHECK_SLOTS_MAX = exports.COAT_CHECK_CONTENT_TYPE_MAX = exports.COAT_CHECK_SUMMARY_MAX = exports.COAT_CHECK_CONTENT_MAX = exports.COAT_CHECK_TICKET_MAX = exports.COAT_CHECK_TICKET_REGEX = exports.PLAYER_STATE_SLOTS_MAX = exports.PLAYER_STATE_CONTENT_MAX = exports.PLAYER_STATE_KEY_MAX = exports.PLAYER_STATE_KEY_REGEX = exports.PLAYER_STATE_DEFAULT_KEY = exports.PART_MAX = exports.MESSAGE_MAX = exports.STAGE_PLAYERS_MAX = exports.STAGE_NAME_MAX = exports.ENSEMBLE_NAME_REGEX = exports.PLAYER_NAME_MAX = exports.PLAYER_NAME_REGEX = void 0;
|
|
8
8
|
exports.shouldIncludeInBroadcast = shouldIncludeInBroadcast;
|
|
9
9
|
exports.validatePlayerName = validatePlayerName;
|
|
10
10
|
exports.validateEnsembleName = validateEnsembleName;
|
|
@@ -43,6 +43,37 @@ exports.PLAYER_STATE_KEY_MAX = 32;
|
|
|
43
43
|
exports.PLAYER_STATE_CONTENT_MAX = 32 * 1024;
|
|
44
44
|
/** Maximum number of populated slots per player. Saturation rejects with `PlayerStateSlotsFull`. */
|
|
45
45
|
exports.PLAYER_STATE_SLOTS_MAX = 4;
|
|
46
|
+
// ── Coat-check (#318, ADR 0008) ────────────────────────────────────────────
|
|
47
|
+
//
|
|
48
|
+
// Ensemble-shared, ticket-keyed stash for content too large to inline in a
|
|
49
|
+
// `cue` message body. Lives on per-ensemble Maestro workflow state.
|
|
50
|
+
//
|
|
51
|
+
// Sizing per the #318 architect verdict (which adjusts ADR 0008's 50-slot
|
|
52
|
+
// proposal):
|
|
53
|
+
// per-entry 32 KiB × max 20 slots → 640 KiB structural max per ensemble.
|
|
54
|
+
// That lands at ~16 % of Temporal's 4 MiB CAN-payload ceiling — comfortable
|
|
55
|
+
// headroom on top of existing maestro state (~316 KiB). Saturation refuses
|
|
56
|
+
// with a `CoatCheckSlotsFull` ApplicationFailure rather than evicting LRU
|
|
57
|
+
// — cross-host scope makes silent peer eviction a sharper footgun than
|
|
58
|
+
// the rejection's diagnostic UX.
|
|
59
|
+
/** Coat-check ticket id pattern. Alphanumeric + underscore + hyphen, generated server-side via `uuid4()`. */
|
|
60
|
+
exports.COAT_CHECK_TICKET_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
61
|
+
/** Maximum coat-check ticket id length. Generous to accommodate uuid4 + future versioning prefixes. */
|
|
62
|
+
exports.COAT_CHECK_TICKET_MAX = 64;
|
|
63
|
+
/** Maximum content size per coat-check entry — 32 KiB. Mirrors `PLAYER_STATE_CONTENT_MAX`. */
|
|
64
|
+
exports.COAT_CHECK_CONTENT_MAX = 32 * 1024;
|
|
65
|
+
/** Maximum summary length per coat-check entry — short, listing-friendly preamble. */
|
|
66
|
+
exports.COAT_CHECK_SUMMARY_MAX = 500;
|
|
67
|
+
/** Maximum free-form contentType string (e.g. `'text/markdown'`). */
|
|
68
|
+
exports.COAT_CHECK_CONTENT_TYPE_MAX = 64;
|
|
69
|
+
/** Maximum populated coat-check slots per ensemble. Saturation rejects with `CoatCheckSlotsFull`. */
|
|
70
|
+
exports.COAT_CHECK_SLOTS_MAX = 20;
|
|
71
|
+
/** Minimum allowed `ttlMs` argument on `coat_check_put` — 1 hour. */
|
|
72
|
+
exports.COAT_CHECK_TTL_MIN_MS = 60 * 60 * 1000;
|
|
73
|
+
/** Maximum allowed `ttlMs` argument on `coat_check_put` — 30 days. */
|
|
74
|
+
exports.COAT_CHECK_TTL_MAX_MS = 30 * 24 * 60 * 60 * 1000;
|
|
75
|
+
/** Default `ttlMs` when caller omits the argument — 7 days. */
|
|
76
|
+
exports.COAT_CHECK_TTL_DEFAULT_MS = 7 * 24 * 60 * 60 * 1000;
|
|
46
77
|
/**
|
|
47
78
|
* Maximum length of the ensemble description set via
|
|
48
79
|
* `set_ensemble_description` (#399 W1, Q5.1). Soft cap — the MCP tool
|
package/dist/utils/worktree.d.ts
CHANGED
|
@@ -82,5 +82,22 @@ export declare function createWorktree(opts: CreateWorktreeOpts): CreateWorktree
|
|
|
82
82
|
export declare function installDependencies(worktreePath: string, timeoutMs?: number): void;
|
|
83
83
|
/**
|
|
84
84
|
* Remove a git worktree.
|
|
85
|
+
*
|
|
86
|
+
* Throws if the worktree directory survives the removal (#594). On Windows,
|
|
87
|
+
* `git worktree remove --force` deletes `.git/worktrees/<name>` metadata
|
|
88
|
+
* *first*, then `rmdir`s the directory — so when a native `.node` module
|
|
89
|
+
* inside the worktree is memory-mapped by a live process, the directory
|
|
90
|
+
* deletion fails while the metadata is already gone. The pre-#594 code
|
|
91
|
+
* swallowed that failure (log-only, no throw), so the caller reported
|
|
92
|
+
* success and Temporal state diverged from disk. Now the post-removal
|
|
93
|
+
* `existsSync` check turns a half-removal into a hard error the caller can
|
|
94
|
+
* surface.
|
|
95
|
+
*
|
|
96
|
+
* `gitRoot` scopes the `git worktree remove` invocation to the owning
|
|
97
|
+
* repository. Pre-#594 the command ran with no `cwd`, so it only worked when
|
|
98
|
+
* `process.cwd()` happened to be the right repo — fragile, and wrong outright
|
|
99
|
+
* when the conductor's cwd is a different checkout than the worktree's repo.
|
|
100
|
+
* Callers should pass `entry.gitRoot` from the `WorktreeEntry`; it defaults to
|
|
101
|
+
* `process.cwd()` for backward compatibility.
|
|
85
102
|
*/
|
|
86
|
-
export declare function removeWorktree(worktreePath: string): void;
|
|
103
|
+
export declare function removeWorktree(worktreePath: string, gitRoot?: string): void;
|