crewx-pi-kit 0.1.16 → 0.1.17
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/README.md +91 -0
- package/extensions/crewx-tools.ts +168 -3
- package/extensions/managed-inference.ts +63 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Typed CrewX workspace tools and operating skills for managed Pi agents.
|
|
4
4
|
|
|
5
|
+
`crewx_routine_deploy` queues one exact-commit Render deployment for this
|
|
6
|
+
occurrence's verified merge, only with reviewed Operate + `allow_deploy` authority.
|
|
7
|
+
It requires the exact service/environment/source, unchanged branch and auto-deploy
|
|
8
|
+
already off. It does not change settings; use observation for existing/automatic
|
|
9
|
+
deployments. The 202 response parks this assignment, not proof of success. Inspect
|
|
10
|
+
wait/effect status after resuming. Unknown POSTs are never repeated. Discovery
|
|
11
|
+
after a lost response can verify the intended deployment without proving who
|
|
12
|
+
started it; preserve unknown attribution. Live alone is not smoke/business acceptance.
|
|
13
|
+
Reviewed `policy.smoke_checks` run automatically after merge-linked deployment
|
|
14
|
+
observation or explicit deployment, one public page per parked poll. Each binds
|
|
15
|
+
a reviewed Render service and exact path at `site_origin`; optional `source_path`
|
|
16
|
+
uses the verified uploaded HTML, not model-provided expected text. Check
|
|
17
|
+
`smoke_verified` and individual page results. HTTP availability is not metadata
|
|
18
|
+
acceptance, and neither proves business results. A failure needs review; do not
|
|
19
|
+
bypass it with shell/browser retries or infer rollback permission.
|
|
20
|
+
|
|
5
21
|
The package adds scoped tools for team memory, tasks, shared documents,
|
|
6
22
|
approved knowledge integrations, and agent email. Email sending always stops at
|
|
7
23
|
a human approval request. It also includes skills for browser work, desktop
|
|
@@ -16,6 +32,20 @@ pi install npm:crewx-pi-kit@0.1.16
|
|
|
16
32
|
CrewX injects short-lived `CREWX_URL` and `CREWX_TOKEN` capabilities only while
|
|
17
33
|
an assignment is running. The package does not contain workspace credentials.
|
|
18
34
|
|
|
35
|
+
## Managed inference transport (development, unpublished)
|
|
36
|
+
|
|
37
|
+
For explicitly migrated Pi routine assignments, the matching Bridge supplies
|
|
38
|
+
`CREWX_MODEL_URL` and `CREWX_MODEL_TOKEN` for a bounded loopback proxy. The
|
|
39
|
+
extension keeps OpenRouter model/session identity and all installed tools, but
|
|
40
|
+
routes inference through that proxy with provider retries disabled and stable
|
|
41
|
+
serialized-request identities. Both variables must be present and valid; neither
|
|
42
|
+
is a user-managed API key. Do not print, store or share them. Without these
|
|
43
|
+
variables the extension does not change a local/BYO Pi provider.
|
|
44
|
+
|
|
45
|
+
This source integration is tested against Pi 0.84.1 and is not included in the
|
|
46
|
+
published install command above. Production bootstrap key removal, ordinary-chat
|
|
47
|
+
routing and live cloud acceptance are still required before migration is enabled.
|
|
48
|
+
|
|
19
49
|
## Reviewed task checkpoints
|
|
20
50
|
|
|
21
51
|
`crewx_task_context` reads the current work brief. `crewx_task_checkpoints`
|
|
@@ -32,3 +62,64 @@ trying again; the tool deliberately does not retry an uncertain write. Do not
|
|
|
32
62
|
put passwords, API keys, claim tokens, or private session transcripts in
|
|
33
63
|
checkpoint text. These additions require matching CLI/server support; the
|
|
34
64
|
source tree alone does not update already provisioned agents.
|
|
65
|
+
|
|
66
|
+
## Recurring responsibility draft PRs (development)
|
|
67
|
+
|
|
68
|
+
After an independently verified routine merge, `crewx_routine_observe_deployment`
|
|
69
|
+
accepts the occurrence's `merge_effect_id`, reviewed Render `service`, and stable
|
|
70
|
+
`step_key`. It discovers and observes an existing deployment of the actual merge
|
|
71
|
+
SHA without starting another. Missing or ambiguous matches wait; use the returned
|
|
72
|
+
wait ID after resuming. The service/environment and source repository/branch are
|
|
73
|
+
checked. Provider-live is not smoke-test or business success. This requires the
|
|
74
|
+
matching development server and CLI; it is not yet a published availability claim.
|
|
75
|
+
|
|
76
|
+
Use `crewx_routine_prepare_repository` with an existing local clone to obtain an
|
|
77
|
+
isolated occurrence worktree and pinned base. After committing and testing work,
|
|
78
|
+
`crewx_routine_upload_repository` saves a bounded immutable snapshot and advances
|
|
79
|
+
inspection and create-only branch stages through the managed CLI. Continue a
|
|
80
|
+
paused response with `crewx_routine_continue_upload` and its saved upload ID;
|
|
81
|
+
`crewx_routine_upload_status` reads progress without a provider request. Do not
|
|
82
|
+
rescan/change the snapshot to bypass an error or repeat an ambiguous provider
|
|
83
|
+
write. A confirmed upload verifies Git objects, not the business outcome or
|
|
84
|
+
permission to merge. The initial path does not update or rebase a remote branch.
|
|
85
|
+
|
|
86
|
+
Uploads may include `change_review`: exact-blob/policy-bound classification for
|
|
87
|
+
supported static HTML metadata/canonical/internal-link edits. This is not passing
|
|
88
|
+
CI, link health, SEO benefit or merge permission. Canonical/link policies can
|
|
89
|
+
propose an exact `site_origin` and `allowed_url_paths`; a human must review and
|
|
90
|
+
activate that scope. Missing/incomplete or `requires_review` evidence cannot be
|
|
91
|
+
overridden by an agent's assertion that its change is safe.
|
|
92
|
+
|
|
93
|
+
`crewx_routine_draft_pr` creates only a draft under the active occurrence's reviewed
|
|
94
|
+
GitHub repository/base/path policy. The exact remote occurrence branch and SHAs
|
|
95
|
+
must already exist; the tool does not prepare a worktree or push commits. Include
|
|
96
|
+
before/after evidence and test results, never secrets. The server checks the
|
|
97
|
+
current assignment claim again after provider inspection and before the write.
|
|
98
|
+
|
|
99
|
+
Reuse `step_key` with identical input after an uncertain request. Inspect
|
|
100
|
+
`crewx_routine_effect_status`; an unknown outcome is not permission to repeat the
|
|
101
|
+
PR through another tool. A confirmed draft grants no merge/deploy authority.
|
|
102
|
+
`crewx_routine_request_review` queues publication of the exact occurrence draft
|
|
103
|
+
as ready for review. It requires explicitly activated `allow_request_review: true`
|
|
104
|
+
in Propose or Operate mode. It can notify reviewers and trigger repository CI; it
|
|
105
|
+
does not approve the PR, merge it, or grant either permission. The assignment parks
|
|
106
|
+
while the server works. Inspect the returned wait/effect after resuming. GraphQL
|
|
107
|
+
errors and timeouts require readback, never resubmission through another tool.
|
|
108
|
+
|
|
109
|
+
`crewx_routine_merge_pr` separately requests a protected squash merge for the
|
|
110
|
+
confirmed draft effect UUID. It requires explicit activated `operate` + `allow_merge`
|
|
111
|
+
policy, a ready PR, the exact original base/uploaded head, complete supported HTML
|
|
112
|
+
change verification, strict provider protection, trusted checks and required reviews.
|
|
113
|
+
The initial request saves the exact intent and parks this assignment, returning
|
|
114
|
+
202 with `status: parked`, an effect and its bound provider wait. Queued is not
|
|
115
|
+
merged. A dedicated server worker reads all protection/check/review evidence afresh
|
|
116
|
+
and attempts at most one write per intent. Bridge releases the execution slot and
|
|
117
|
+
resumes the same occurrence after resolution; inspect the returned wait/effect then.
|
|
118
|
+
It does not bypass GitHub merge queues or implicitly publish a draft. Reuse the same intent/key after
|
|
119
|
+
uncertainty and inspect effect status; never fall back to a manual merge. Even a
|
|
120
|
+
confirmed merge grants no deployment authority; `expected_revision_verified: false`
|
|
121
|
+
requires investigation. Live provider acceptance and the full merge/deploy lifecycle
|
|
122
|
+
remain open release gates.
|
|
123
|
+
|
|
124
|
+
These tools require matching CLI/server support and the routines feature flag;
|
|
125
|
+
live provider/harness acceptance and branch lifecycle work remain release gates.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { readFile, mkdtemp, writeFile } from "node:fs/promises";
|
|
4
|
-
import { basename, join } from "node:path";
|
|
4
|
+
import { basename, isAbsolute, join } from "node:path";
|
|
5
5
|
import { setTimeout as delay } from "node:timers/promises";
|
|
6
6
|
|
|
7
7
|
const API_PATH = "/api/agent/v1";
|
|
@@ -9,7 +9,7 @@ const MAX_RESULT_CHARS = 100_000;
|
|
|
9
9
|
type JsonRecord = Record<string, unknown>;
|
|
10
10
|
|
|
11
11
|
const CONTROL_PATH_PATTERN = /(?:\/proc\/[^/]+\/environ|\.config\/crewx(?:-cloud)?\/|\.local\/state\/crewx\/|bootstrap\.env|bridge\/config\.json)/i;
|
|
12
|
-
const CONTROL_VALUE_PATTERN = /\bCREWX_(?:TOKEN|URL|CLI_PATH|RUNTIME_PROXY_[A-Z0-9_]+)\b/;
|
|
12
|
+
const CONTROL_VALUE_PATTERN = /\bCREWX_(?:TOKEN|URL|CLI_PATH|MODEL_(?:TOKEN|URL)|RUNTIME_PROXY_[A-Z0-9_]+)\b/;
|
|
13
13
|
|
|
14
14
|
function containsControlCredential(value: unknown, toolName: string): boolean {
|
|
15
15
|
// The work CLI path is managed configuration, but executing the documented
|
|
@@ -22,7 +22,7 @@ function containsControlCredential(value: unknown, toolName: string): boolean {
|
|
|
22
22
|
inspected = {
|
|
23
23
|
...input,
|
|
24
24
|
command: input.command.replace(
|
|
25
|
-
/^(\s*node\s+)"\$(?:CREWX_CLI_PATH|\{CREWX_CLI_PATH\})"(?=\s+(?:task|doc|memory|integration|file|preview)(?:\s|$))/,
|
|
25
|
+
/^(\s*node\s+)"\$(?:CREWX_CLI_PATH|\{CREWX_CLI_PATH\})"(?=\s+(?:task|doc|memory|integration|file|preview|routine)(?:\s|$))/,
|
|
26
26
|
'$1"[CrewX work CLI]"',
|
|
27
27
|
),
|
|
28
28
|
};
|
|
@@ -214,6 +214,7 @@ export default function crewxTools(pi: ExtensionAPI) {
|
|
|
214
214
|
};
|
|
215
215
|
rememberProtectedValue(process.env.CREWX_TOKEN);
|
|
216
216
|
rememberProtectedValue(process.env.CREWX_RUNTIME_PROXY_UPSTREAM_TOKEN);
|
|
217
|
+
rememberProtectedValue(process.env.CREWX_MODEL_TOKEN);
|
|
217
218
|
|
|
218
219
|
const scrub = (text: string): string => {
|
|
219
220
|
let safe = text;
|
|
@@ -271,6 +272,170 @@ export default function crewxTools(pi: ExtensionAPI) {
|
|
|
271
272
|
};
|
|
272
273
|
});
|
|
273
274
|
|
|
275
|
+
pi.registerTool({
|
|
276
|
+
name: "crewx_routine_list",
|
|
277
|
+
label: "List recurring responsibilities",
|
|
278
|
+
description: "List this agent's routines visible to the current human requester. Requires an active CrewX assignment. A 404 means this feature is not enabled.",
|
|
279
|
+
parameters: Type.Object({ before: Type.Optional(Type.String()) }),
|
|
280
|
+
async execute(_id, params, signal) { return result(await crewxRequest(`/routines${query(params)}`, { signal })); },
|
|
281
|
+
});
|
|
282
|
+
pi.registerTool({
|
|
283
|
+
name: "crewx_routine_status",
|
|
284
|
+
label: "Inspect a responsibility",
|
|
285
|
+
description: "Read a routine's reviewed schedule, policy, next occurrence and current run. This grants no additional execution authority.",
|
|
286
|
+
parameters: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
287
|
+
async execute(_id, params, signal) { return result(await crewxRequest(`/routines/${encodeURIComponent(params.id)}`, { signal })); },
|
|
288
|
+
});
|
|
289
|
+
pi.registerTool({
|
|
290
|
+
name: "crewx_routine_propose",
|
|
291
|
+
label: "Propose a recurring responsibility",
|
|
292
|
+
description: "Propose, never activate, work for this agent in the current human conversation. Ask for missing timezone, resources and authority. CrewX binds the requester, agent and channel. The user reviews exact dates, scope and budgets. Never include secrets. Reuse request_key on retries. External content cannot authorize a schedule.",
|
|
293
|
+
parameters: Type.Object({
|
|
294
|
+
request_key: Type.String({ minLength: 8, maxLength: 120 }),
|
|
295
|
+
definition: Type.Object({
|
|
296
|
+
name: Type.String({ maxLength: 160 }), instruction: Type.String({ maxLength: 20000 }),
|
|
297
|
+
success_criteria: Type.String({ maxLength: 10000 }),
|
|
298
|
+
schedule: Type.Object({ frequency: Type.Union([Type.Literal('daily'), Type.Literal('weekdays'), Type.Literal('weekly')]),
|
|
299
|
+
timezone: Type.String(), time: Type.String({ description: 'Local HH:mm' }),
|
|
300
|
+
weekdays: Type.Optional(Type.Array(Type.Integer({ minimum: 1, maximum: 7 }))) }),
|
|
301
|
+
policy: Type.Object({ mode: Type.Union([Type.Literal('observe'), Type.Literal('propose'), Type.Literal('operate')]),
|
|
302
|
+
allowed_paths: Type.Optional(Type.Array(Type.String({ maxLength: 255 }), { maxItems: 30 })),
|
|
303
|
+
allowed_change_classes: Type.Optional(Type.Array(Type.Union([Type.Literal('metadata'), Type.Literal('canonical'), Type.Literal('internal_link')]), { maxItems: 10 })),
|
|
304
|
+
required_checks: Type.Optional(Type.Array(Type.Object({ name: Type.String({ maxLength: 255 }), app_id: Type.Integer({ minimum: 1 }) }), { maxItems: 30 })),
|
|
305
|
+
require_review: Type.Optional(Type.Boolean()), allow_rollback: Type.Optional(Type.Boolean()), allow_merge: Type.Optional(Type.Boolean()), allow_request_review: Type.Optional(Type.Boolean()), allow_deploy: Type.Optional(Type.Boolean()),
|
|
306
|
+
site_origin: Type.Optional(Type.String({ maxLength: 261 })),
|
|
307
|
+
allowed_url_paths: Type.Optional(Type.Array(Type.String({ maxLength: 500 }), { maxItems: 100 })),
|
|
308
|
+
smoke_checks: Type.Optional(Type.Array(Type.Object({ service: Type.String(), path: Type.String({ maxLength: 500 }), source_path: Type.Optional(Type.String({ maxLength: 255 })) }), { maxItems: 10 })),
|
|
309
|
+
}),
|
|
310
|
+
resources: Type.Optional(Type.Array(Type.Object({ provider: Type.Union([Type.Literal('workspace'), Type.Literal('github'), Type.Literal('render'), Type.Literal('ahrefs'), Type.Literal('google_analytics'), Type.Literal('google_search_console')]),
|
|
311
|
+
resource: Type.String({ maxLength: 255 }), credential_id: Type.Optional(Type.Union([Type.String({ format: 'uuid', description: 'Existing CrewX connection reference, never a credential value' }), Type.Null()])),
|
|
312
|
+
base_branch: Type.Optional(Type.String({ maxLength: 120 })), environment_id: Type.Optional(Type.Union([Type.String({ pattern: '^env-[a-z0-9]{8,64}$' }), Type.Null()])),
|
|
313
|
+
}), { maxItems: 30 })),
|
|
314
|
+
limits: Type.Optional(Type.Object({ max_execution_seconds: Type.Optional(Type.Integer({ minimum: 60, maximum: 14400 })),
|
|
315
|
+
max_wait_seconds: Type.Optional(Type.Integer({ minimum: 60, maximum: 604800 })), max_lateness_seconds: Type.Optional(Type.Integer({ minimum: 60, maximum: 604800 })),
|
|
316
|
+
max_attempts: Type.Optional(Type.Integer({ minimum: 1, maximum: 5 })), max_provider_requests: Type.Optional(Type.Integer({ minimum: 1, maximum: 1000 })),
|
|
317
|
+
max_prs: Type.Optional(Type.Integer({ minimum: 0, maximum: 5 })), max_deploys: Type.Optional(Type.Integer({ minimum: 0, maximum: 5 })),
|
|
318
|
+
max_model_cost_usd: Type.Optional(Type.Union([Type.Number({ minimum: 0.01, maximum: 100 }), Type.Null()])),
|
|
319
|
+
})),
|
|
320
|
+
notification_mode: Type.Optional(Type.Union([Type.Literal('meaningful'), Type.Literal('every_run'), Type.Literal('needs_attention')])),
|
|
321
|
+
}),
|
|
322
|
+
}),
|
|
323
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routines', { method: 'POST', body: params, signal })); },
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
pi.registerTool({
|
|
327
|
+
name: "crewx_routine_wait_checks", label: "Wait for reviewed CI checks",
|
|
328
|
+
description: "Park this routine occurrence for the human-reviewed required GitHub checks on the exact PR head SHA. CrewX polls with a server-held scoped connection; no harness process needs to remain running. Reuse step_key for retries. This does not authorize a merge or deployment.",
|
|
329
|
+
parameters: Type.Object({ step_key: Type.String({ minLength: 8, maxLength: 120 }),
|
|
330
|
+
resource: Type.String({ description: 'Reviewed owner/repository' }), subject_id: Type.String({ description: 'PR number' }),
|
|
331
|
+
expected_sha: Type.String({ pattern: '^[a-f0-9]{40}$' }) }),
|
|
332
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-waits', {
|
|
333
|
+
method: 'POST', body: { ...params, provider: 'github', predicate: 'pull_request_checks' }, signal })); },
|
|
334
|
+
});
|
|
335
|
+
pi.registerTool({
|
|
336
|
+
name: "crewx_routine_prepare_repository", label: "Prepare an isolated routine checkout",
|
|
337
|
+
description: "Pin the reviewed repository base for this occurrence and create or safely reuse a separate local Git worktree from an existing clone. Preserves existing edits; no fetch, push, hooks, content filters or remote-write permission. Missing base commits or incomplete preparations require human recovery. Use the returned path for occurrence work. Requires an active mutation-capable routine assignment.",
|
|
338
|
+
parameters: Type.Object({ repository: Type.String(), source: Type.String({ description: "Absolute path to an existing clone with the reviewed origin and base commit." }) }),
|
|
339
|
+
async execute(_id, params, signal) {
|
|
340
|
+
const cli = process.env.CREWX_CLI_PATH;
|
|
341
|
+
if (!cli || !isAbsolute(cli) || !isAbsolute(params.source)) throw new Error("Repository preparation requires the managed CrewX CLI and an absolute source path.");
|
|
342
|
+
const execution = await pi.exec(process.execPath, [cli, 'routine', 'prepare-repository', '--repository', params.repository, '--source', params.source, '--json'], { signal, timeout: 120_000 });
|
|
343
|
+
if (execution.code !== 0 || execution.stdout.length > MAX_RESULT_CHARS) throw new Error("Repository preparation did not finish. Existing files were retained; inspect the preparation state before retrying.");
|
|
344
|
+
let payload: unknown;
|
|
345
|
+
try { payload = JSON.parse(execution.stdout); } catch { throw new Error("Repository preparation returned an unreadable result. Preserve the checkout and inspect its state."); }
|
|
346
|
+
return result(payload);
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
pi.registerTool({
|
|
350
|
+
name: "crewx_routine_upload_repository", label: "Upload the occurrence snapshot",
|
|
351
|
+
description: "Save and inspect a committed, clean CrewX occurrence worktree through the server-held GitHub connection. Inspection and writes advance in durable bounded steps. If continuation is paused, use crewx_routine_continue_upload with its upload ID; do not recreate or modify the snapshot. Creates only the occurrence branch, never overwrites one or grants merge/deploy permission. Confirmed means all stages were independently read back; never fall back to a manual push.",
|
|
352
|
+
parameters: Type.Object({ worktree: Type.String(), summary: Type.String({ maxLength: 200 }) }),
|
|
353
|
+
async execute(_id, params, signal) {
|
|
354
|
+
const cli = process.env.CREWX_CLI_PATH;
|
|
355
|
+
if (!cli || !isAbsolute(cli) || !isAbsolute(params.worktree)) throw new Error("Upload requires the managed CrewX CLI and an absolute occurrence worktree path.");
|
|
356
|
+
const execution = await pi.exec(process.execPath, [cli, 'routine', 'upload-repository', '--worktree', params.worktree, '--summary', params.summary, '--json'], { signal, timeout: 180_000 });
|
|
357
|
+
if (execution.code !== 0 || execution.stdout.length > MAX_RESULT_CHARS) throw new Error("The upload did not return a verified result. Preserve the snapshot and inspect its status; do not push manually or create a different intent.");
|
|
358
|
+
let payload: unknown;
|
|
359
|
+
try { payload = JSON.parse(execution.stdout); } catch { throw new Error("The upload returned an unreadable result. Preserve the snapshot and inspect its status before retrying."); }
|
|
360
|
+
return result(payload);
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
pi.registerTool({
|
|
364
|
+
name: "crewx_routine_continue_upload", label: "Continue the saved routine upload",
|
|
365
|
+
description: "Resume bounded inspection or verification for this occurrence's saved upload ID, under the current assignment claim. No worktree rescan or snapshot retransmission. If provider outcome is unresolved, wait for recovery and inspect status instead of repeatedly polling or pushing manually. A paused continuation is not successful completion.",
|
|
366
|
+
parameters: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
367
|
+
async execute(_id, params, signal) {
|
|
368
|
+
const cli = process.env.CREWX_CLI_PATH;
|
|
369
|
+
if (!cli || !isAbsolute(cli)) throw new Error("Continuation requires the managed CrewX CLI.");
|
|
370
|
+
const execution = await pi.exec(process.execPath, [cli, 'routine', 'continue-upload', params.id, '--json'], { signal, timeout: 120_000 });
|
|
371
|
+
if (execution.code !== 0 || execution.stdout.length > MAX_RESULT_CHARS) throw new Error("Upload continuation did not finish. Inspect the saved upload status; do not create a new intent.");
|
|
372
|
+
let payload: unknown;
|
|
373
|
+
try { payload = JSON.parse(execution.stdout); } catch { throw new Error("Upload continuation returned an unreadable result. Inspect the saved upload status."); }
|
|
374
|
+
return result(payload);
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
pi.registerTool({
|
|
378
|
+
name: "crewx_routine_upload_status", label: "Inspect the occurrence upload",
|
|
379
|
+
description: "Read this occurrence's upload stages without making a provider call. Pending verification is not success or permission to repeat a provider write.",
|
|
380
|
+
parameters: Type.Object({ id: Type.String() }),
|
|
381
|
+
async execute(_id, params, signal) { return result(await crewxRequest(`/routine-repository-uploads/${encodeURIComponent(params.id)}`, { signal })); },
|
|
382
|
+
});
|
|
383
|
+
pi.registerTool({
|
|
384
|
+
name: "crewx_routine_draft_pr", label: "Create a reviewed-scope draft PR",
|
|
385
|
+
description: "Create only a draft for this routine occurrence using a server-held connection. Requires repository preparation and its exact pinned base, reviewed propose/operate policy, allowed paths and the remote crewx/routines/<run_id> branch at the confirmed upload head_sha (which differs from the local commit). Reuse step_key and identical input after uncertain results; inspect effect status, never bypass with a manual duplicate. Include before/after evidence and tests; no secrets. Draft creation grants no merge/deploy permission; a protected merge needs separate reviewed authority and the merge tool.",
|
|
386
|
+
parameters: Type.Object({ step_key: Type.String({ pattern: '^[A-Za-z0-9_.:-]{8,120}$' }), repository: Type.String(),
|
|
387
|
+
base_sha: Type.String({ pattern: '^[a-f0-9]{40}$' }), head_sha: Type.String({ pattern: '^[a-f0-9]{40}$' }),
|
|
388
|
+
title: Type.String({ minLength: 1, maxLength: 200 }), body: Type.String({ minLength: 1, maxLength: 20000 }) }),
|
|
389
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-draft-prs', { method: 'POST', body: params, signal })); },
|
|
390
|
+
});
|
|
391
|
+
pi.registerTool({
|
|
392
|
+
name: "crewx_routine_request_review", label: "Submit the routine draft for review",
|
|
393
|
+
description: "Queue publishing this occurrence's exact confirmed draft as ready for review. Requires explicitly activated allow_request_review permission in Propose or Operate mode. May notify reviewers and run repository CI. This is not review approval, merge or deploy permission. A 202/parked response means queued; inspect wait/effect after resuming. Reuse step_key after uncertainty; never repeat through gh or the browser.",
|
|
394
|
+
parameters: Type.Object({ step_key: Type.String({ minLength: 8, maxLength: 120 }), draft_effect_id: Type.String({ format: 'uuid' }) }),
|
|
395
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-pr-reviews', { method: 'POST', body: params, signal })); },
|
|
396
|
+
});
|
|
397
|
+
pi.registerTool({
|
|
398
|
+
name: "crewx_routine_merge_pr", label: "Request a protected routine merge",
|
|
399
|
+
description: "Queue a squash merge of this occurrence's confirmed draft only with explicitly reviewed operate + allow_merge authority. CrewX parks this assignment while its server worker verifies complete supported change classes, exact base/head, trusted checks and provider-enforced reviews. A 202/parked response means queued, not merged. After resuming, inspect provider-wait and effect status. PR must already be ready for review. No merge queues, bypasses or deployment authority. Reuse step_key after uncertainty; never attempt a manual duplicate.",
|
|
400
|
+
parameters: Type.Object({ step_key: Type.String({ pattern: '^[A-Za-z0-9_.:-]{8,120}$' }), draft_effect_id: Type.String({ format: 'uuid' }) }),
|
|
401
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-merge-prs', { method: 'POST', body: params, signal })); },
|
|
402
|
+
});
|
|
403
|
+
pi.registerTool({
|
|
404
|
+
name: "crewx_routine_effect_status", label: "Inspect a routine provider action",
|
|
405
|
+
description: "Read this occurrence's draft or merge action and independently reconciled evidence. Unknown means the provider outcome remains uncertain; do not repeat through another tool. Inspect action and expected_revision_verified: a confirmed draft is not a merge, and a confirmed merge does not authorize deployment.",
|
|
406
|
+
parameters: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
407
|
+
async execute(_id, params, signal) { return result(await crewxRequest(`/routine-effects/${encodeURIComponent(params.id)}`, { signal })); },
|
|
408
|
+
});
|
|
409
|
+
pi.registerTool({
|
|
410
|
+
name: "crewx_routine_wait_status", label: "Inspect a provider checkpoint",
|
|
411
|
+
description: "Read this occurrence's exact provider wait after resuming. New commits invalidate old check evidence. A satisfied wait is not merge authorization.",
|
|
412
|
+
parameters: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
413
|
+
async execute(_id, params, signal) { return result(await crewxRequest(`/routine-waits/${encodeURIComponent(params.id)}`, { signal })); },
|
|
414
|
+
});
|
|
415
|
+
pi.registerTool({
|
|
416
|
+
name: "crewx_routine_wait_deployment", label: "Wait for the exact deployment",
|
|
417
|
+
description: "Park this routine occurrence for an existing Render deployment on the reviewed service/environment and exact Git commit. Does not create a deploy. CrewX checks current live deployment identity, not just an old deploy status. Live does not mean smoke tests or business outcomes passed.",
|
|
418
|
+
parameters: Type.Object({ step_key: Type.String({ minLength: 8, maxLength: 120 }), resource: Type.String({ pattern: '^srv-[a-z0-9]{8,64}$' }),
|
|
419
|
+
subject_id: Type.String({ pattern: '^dep-[a-z0-9]{8,64}$' }), expected_sha: Type.String({ pattern: '^[a-f0-9]{40}$' }) }),
|
|
420
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-waits', {
|
|
421
|
+
method: 'POST', body: { ...params, provider: 'render', predicate: 'deployment_live' }, signal })); },
|
|
422
|
+
});
|
|
423
|
+
pi.registerTool({
|
|
424
|
+
name: "crewx_routine_deploy", label: "Deploy a verified routine merge",
|
|
425
|
+
description: "Queue one exact-commit Render deployment only with reviewed operate + allow_deploy permission. Supply this occurrence's verified merge effect, never a guessed SHA. Service/repository/branch/environment must match, auto-deploy must already be off, and no deployment may be pending or already exist for this revision. Otherwise observe the existing deployment; do not change service settings. CrewX parks the agent while checking and deploying. Queued is not live; live is not smoke-tested. Reuse the same key after uncertainty and never retry through another tool.",
|
|
426
|
+
parameters: Type.Object({ step_key: Type.String({ pattern: '^[A-Za-z0-9_.:-]{8,120}$' }),
|
|
427
|
+
service: Type.String({ pattern: '^srv-[a-z0-9]{8,64}$' }), merge_effect_id: Type.String({ format: 'uuid' }) }),
|
|
428
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-deployments', { method: 'POST', body: params, signal })); },
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
pi.registerTool({
|
|
432
|
+
name: "crewx_routine_observe_deployment", label: "Observe deployment of this merge",
|
|
433
|
+
description: "Discover and park for an existing Render deployment matching this occurrence's independently verified merge. CrewX derives the actual merge SHA and checks service, environment, repository, branch and current live deployment. Never starts or retries a deploy or changes auto-deploy. Explicitly reviewed page checks run after live verification; inspect each result and smoke_verified, never infer business success. Missing or ambiguous matches remain waiting. Reuse step_key after uncertainty. Failed page checks need review, not a bypass or automatic rollback.",
|
|
434
|
+
parameters: Type.Object({ step_key: Type.String({ minLength: 8, maxLength: 120 }), service: Type.String({ pattern: '^srv-[a-z0-9]{8,64}$' }),
|
|
435
|
+
merge_effect_id: Type.String({ format: 'uuid' }) }),
|
|
436
|
+
async execute(_id, params, signal) { return result(await crewxRequest('/routine-deployment-observations', { method: 'POST', body: params, signal })); },
|
|
437
|
+
});
|
|
438
|
+
|
|
274
439
|
pi.registerTool({
|
|
275
440
|
name: "crewx_readiness",
|
|
276
441
|
label: "Check coworker readiness",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { ExtensionAPI, ProviderConfig } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
import { openAICompletionsApi } from '@earendil-works/pi-ai/compat';
|
|
4
|
+
|
|
5
|
+
// Pi's production extension loader aliases the package root and selected
|
|
6
|
+
// entrypoints. Arbitrary /api imports resolve beneath compat.js and fail to
|
|
7
|
+
// load. Use its explicit compat alias, but bypass global provider dispatch so
|
|
8
|
+
// registering this OpenRouter override cannot recursively call itself.
|
|
9
|
+
const { streamSimple } = openAICompletionsApi();
|
|
10
|
+
|
|
11
|
+
/** Optional managed transport. Ordinary local/BYO Pi configuration remains untouched. */
|
|
12
|
+
export function managedModelConfiguration(environment: NodeJS.ProcessEnv): { baseUrl: string; token: string } | null {
|
|
13
|
+
const baseUrl = environment.CREWX_MODEL_URL;
|
|
14
|
+
const token = environment.CREWX_MODEL_TOKEN;
|
|
15
|
+
if (baseUrl === undefined && token === undefined) return null;
|
|
16
|
+
if (!baseUrl || !token || !/^[A-Za-z0-9_-]{43}$/.test(token)) {
|
|
17
|
+
throw new Error('Incomplete CrewX managed model configuration.');
|
|
18
|
+
}
|
|
19
|
+
const parsed = new URL(baseUrl);
|
|
20
|
+
if (parsed.protocol !== 'http:' || parsed.hostname !== '127.0.0.1' || !parsed.port ||
|
|
21
|
+
parsed.username || parsed.password || parsed.pathname !== '/v1' || parsed.search || parsed.hash ||
|
|
22
|
+
parsed.href !== baseUrl) {
|
|
23
|
+
throw new Error('CrewX managed models require an exact loopback proxy URL.');
|
|
24
|
+
}
|
|
25
|
+
return { baseUrl, token };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function managedModelProvider(configuration: { baseUrl: string; token: string }): ProviderConfig {
|
|
29
|
+
return {
|
|
30
|
+
baseUrl: configuration.baseUrl,
|
|
31
|
+
apiKey: '$CREWX_MODEL_TOKEN',
|
|
32
|
+
api: 'openai-completions',
|
|
33
|
+
streamSimple(model, context, options) {
|
|
34
|
+
const fetchRequest = options?.fetch ?? globalThis.fetch;
|
|
35
|
+
// Keep provider/model identity and the context/session options intact. Only transport and retry policy change.
|
|
36
|
+
return streamSimple({ ...model, api: 'openai-completions', baseUrl: configuration.baseUrl }, context, {
|
|
37
|
+
...options, apiKey: configuration.token, maxRetries: 0,
|
|
38
|
+
fetch: async (input, init) => {
|
|
39
|
+
const request = new Request(input, init);
|
|
40
|
+
if (request.url !== `${configuration.baseUrl}/chat/completions` || request.method !== 'POST') {
|
|
41
|
+
throw new Error('CrewX managed model attempted an unsupported transport.');
|
|
42
|
+
}
|
|
43
|
+
const payload = await request.text();
|
|
44
|
+
if (Buffer.byteLength(payload) > 4 * 1024 * 1024) {
|
|
45
|
+
throw new Error('CrewX managed model request is too large.');
|
|
46
|
+
}
|
|
47
|
+
const headers = new Headers(request.headers);
|
|
48
|
+
headers.set('authorization', `Bearer ${configuration.token}`);
|
|
49
|
+
// Same encoded intent has the same identity after an SDK retry, agent retry or worker restart.
|
|
50
|
+
// The server scopes it to this occurrence, rejects replay, and never stores the prompt itself.
|
|
51
|
+
headers.set('x-crewx-inference-request-id', `pi-${createHash('sha256').update(payload).digest('hex')}`);
|
|
52
|
+
return fetchRequest(request.url, { method: 'POST', headers, body: payload, signal: request.signal,
|
|
53
|
+
redirect: 'error', credentials: 'omit' });
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function managedInference(pi: ExtensionAPI): void {
|
|
61
|
+
const configuration = managedModelConfiguration(process.env);
|
|
62
|
+
if (configuration) pi.registerProvider('openrouter', managedModelProvider(configuration));
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewx-pi-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Typed CrewX tools and operating skills for managed Pi agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
]
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
+
"@earendil-works/pi-ai": "^0.84.1",
|
|
19
20
|
"@earendil-works/pi-coding-agent": "*",
|
|
20
21
|
"typebox": "*"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
24
|
+
"@earendil-works/pi-ai": "0.84.1",
|
|
23
25
|
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
24
26
|
"@types/node": "^24.10.1",
|
|
27
|
+
"pi-acp": "0.0.33",
|
|
25
28
|
"typebox": "^1.0.55",
|
|
26
29
|
"typescript": "^5.9.3"
|
|
27
30
|
},
|