@rosthq/cli 0.7.56 → 0.7.63
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/dist/doctor.d.ts.map +1 -1
- package/dist/forge-workspace.d.ts +1 -0
- package/dist/forge-workspace.d.ts.map +1 -1
- package/dist/generated/command-manifest.d.ts.map +1 -1
- package/dist/generator/groups.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +878 -375
- package/dist/index.js.map +4 -4
- package/dist/mcp-install.d.ts +1 -0
- package/dist/mcp-install.d.ts.map +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/runner-sandbox.d.ts +5 -0
- package/dist/runner-sandbox.d.ts.map +1 -1
- package/dist/runner-serve.d.ts +73 -0
- package/dist/runner-serve.d.ts.map +1 -1
- package/package.json +1 -1
- package/prompts/how-tos/human-confirmations.md +2 -1
package/dist/index.js
CHANGED
|
@@ -13258,6 +13258,7 @@ var require_main3 = __commonJS({
|
|
|
13258
13258
|
|
|
13259
13259
|
// src/index.ts
|
|
13260
13260
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
13261
|
+
import { createInterface } from "node:readline/promises";
|
|
13261
13262
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
13262
13263
|
|
|
13263
13264
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
@@ -37800,6 +37801,104 @@ import { readFileSync } from "node:fs";
|
|
|
37800
37801
|
import { dirname, join } from "node:path";
|
|
37801
37802
|
import { fileURLToPath } from "node:url";
|
|
37802
37803
|
|
|
37804
|
+
// ../../packages/protocol/src/cron.ts
|
|
37805
|
+
var uintPattern = /^\d+$/;
|
|
37806
|
+
var CRON_FIELD_SPECS = [
|
|
37807
|
+
{ name: "minute", min: 0, max: 59 },
|
|
37808
|
+
{ name: "hour", min: 0, max: 23 },
|
|
37809
|
+
{ name: "day-of-month", min: 1, max: 31 },
|
|
37810
|
+
{ name: "month", min: 1, max: 12 },
|
|
37811
|
+
{ name: "day-of-week", min: 0, max: 7 }
|
|
37812
|
+
];
|
|
37813
|
+
var CRON_FIELD_COUNT = CRON_FIELD_SPECS.length;
|
|
37814
|
+
function parseCronField(raw, spec) {
|
|
37815
|
+
const parts = raw.split(",");
|
|
37816
|
+
const terms = [];
|
|
37817
|
+
for (const part of parts) {
|
|
37818
|
+
const token = part.trim();
|
|
37819
|
+
if (token.length === 0) {
|
|
37820
|
+
return { ok: false, reason: "empty value in list" };
|
|
37821
|
+
}
|
|
37822
|
+
if (token === "*") {
|
|
37823
|
+
terms.push({ kind: "all" });
|
|
37824
|
+
continue;
|
|
37825
|
+
}
|
|
37826
|
+
if (token.startsWith("*/")) {
|
|
37827
|
+
const stepText = token.slice(2);
|
|
37828
|
+
if (!uintPattern.test(stepText)) {
|
|
37829
|
+
return { ok: false, reason: `"${token}" is not a valid step` };
|
|
37830
|
+
}
|
|
37831
|
+
const step = Number(stepText);
|
|
37832
|
+
if (step < 1 || step > spec.max) {
|
|
37833
|
+
return { ok: false, reason: `step in "${token}" must be between 1 and ${spec.max}` };
|
|
37834
|
+
}
|
|
37835
|
+
terms.push({ kind: "step", step });
|
|
37836
|
+
continue;
|
|
37837
|
+
}
|
|
37838
|
+
if (token.includes("-")) {
|
|
37839
|
+
const segments = token.split("-");
|
|
37840
|
+
if (segments.length !== 2 || !uintPattern.test(segments[0] ?? "") || !uintPattern.test(segments[1] ?? "")) {
|
|
37841
|
+
return { ok: false, reason: `"${token}" is not a valid range` };
|
|
37842
|
+
}
|
|
37843
|
+
const start = Number(segments[0]);
|
|
37844
|
+
const end = Number(segments[1]);
|
|
37845
|
+
if (start < spec.min || end > spec.max || start > end) {
|
|
37846
|
+
return { ok: false, reason: `range "${token}" must be within ${spec.min}-${spec.max} and ascending` };
|
|
37847
|
+
}
|
|
37848
|
+
terms.push({ kind: "range", start, end });
|
|
37849
|
+
continue;
|
|
37850
|
+
}
|
|
37851
|
+
if (uintPattern.test(token)) {
|
|
37852
|
+
const value = Number(token);
|
|
37853
|
+
if (value < spec.min || value > spec.max) {
|
|
37854
|
+
return { ok: false, reason: `"${token}" must be between ${spec.min} and ${spec.max}` };
|
|
37855
|
+
}
|
|
37856
|
+
terms.push({ kind: "value", value });
|
|
37857
|
+
continue;
|
|
37858
|
+
}
|
|
37859
|
+
return { ok: false, reason: `"${token}" is not a supported cron token` };
|
|
37860
|
+
}
|
|
37861
|
+
return { ok: true, field: terms };
|
|
37862
|
+
}
|
|
37863
|
+
function parseCronExpression(expression) {
|
|
37864
|
+
const trimmed = expression.trim();
|
|
37865
|
+
if (trimmed.length === 0) {
|
|
37866
|
+
return { ok: false, message: "Schedule must not be empty." };
|
|
37867
|
+
}
|
|
37868
|
+
if (/[\r\n\u2028\u2029]/.test(trimmed)) {
|
|
37869
|
+
return { ok: false, message: "Schedule must be a single-line cron expression." };
|
|
37870
|
+
}
|
|
37871
|
+
if (/[^0-9*/,\t\x20-]/.test(trimmed)) {
|
|
37872
|
+
return { ok: false, message: "Schedule may only contain digits, spaces, and the characters * / , -" };
|
|
37873
|
+
}
|
|
37874
|
+
const rawFields = trimmed.split(/\s+/);
|
|
37875
|
+
if (rawFields.length !== CRON_FIELD_COUNT) {
|
|
37876
|
+
return {
|
|
37877
|
+
ok: false,
|
|
37878
|
+
message: `Schedule must have exactly ${CRON_FIELD_COUNT} fields (minute hour day-of-month month day-of-week); got ${rawFields.length}.`
|
|
37879
|
+
};
|
|
37880
|
+
}
|
|
37881
|
+
const fields = [];
|
|
37882
|
+
for (let i = 0; i < CRON_FIELD_COUNT; i += 1) {
|
|
37883
|
+
const spec = CRON_FIELD_SPECS[i];
|
|
37884
|
+
const result = parseCronField(rawFields[i], spec);
|
|
37885
|
+
if (!result.ok) {
|
|
37886
|
+
return { ok: false, message: `Invalid ${spec.name} field: ${result.reason}.` };
|
|
37887
|
+
}
|
|
37888
|
+
fields.push(result.field);
|
|
37889
|
+
}
|
|
37890
|
+
return { ok: true, cron: { fields } };
|
|
37891
|
+
}
|
|
37892
|
+
var cronExpressionSchema = external_exports.string().trim().min(1).max(120).superRefine((value, ctx) => {
|
|
37893
|
+
if (value.length > 120) {
|
|
37894
|
+
return;
|
|
37895
|
+
}
|
|
37896
|
+
const result = parseCronExpression(value);
|
|
37897
|
+
if (!result.ok) {
|
|
37898
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: result.message });
|
|
37899
|
+
}
|
|
37900
|
+
});
|
|
37901
|
+
|
|
37803
37902
|
// ../../packages/protocol/src/agent-model-config.ts
|
|
37804
37903
|
var agentModelProviderSchema = external_exports.enum(["anthropic"]);
|
|
37805
37904
|
var agentModelEffortSchema = external_exports.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
@@ -37922,7 +38021,7 @@ var dryRunTranscriptSchema = external_exports.object({
|
|
|
37922
38021
|
}).strict();
|
|
37923
38022
|
|
|
37924
38023
|
// ../../packages/protocol/src/scrub.ts
|
|
37925
|
-
var SECRET_SHAPED_VALUE = /(sk-ant-|sk-proj-|sk-live-|sk-[A-Za-z0-9_-]{12,}|
|
|
38024
|
+
var SECRET_SHAPED_VALUE = /(rost_mcp_[A-Za-z0-9_-]{20,}|sk-ant-|sk-proj-|sk-live-|sk-[A-Za-z0-9_-]{12,}|gh[opsur]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{20,}|xox[baoprs]-[A-Za-z0-9-]{10,}|(?:AKIA|ASIA)[0-9A-Z]{16}|BEGIN [A-Z ]*PRIVATE KEY|api[_-]?key\s*=|secret\s*=|password\s*=|authorization:\s*bearer\s+|(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]{20,}|x-api-key\s*:|api[-_ ]?key\s*:\s*\S{3,}|secret\s*:\s*\S{3,}|password\s*:\s*\S{3,}|token\s*:\s*\S{3,})/i;
|
|
37926
38025
|
var SECRET_KEY = /(api[_-]?key|secret|password|token|authorization|credential|private[_-]?key)/i;
|
|
37927
38026
|
function hasSecretShapedContent(value) {
|
|
37928
38027
|
if (typeof value === "string") {
|
|
@@ -38676,7 +38775,7 @@ var agentLifecycleSchema = external_exports.enum([
|
|
|
38676
38775
|
"paused",
|
|
38677
38776
|
"decommissioned"
|
|
38678
38777
|
]);
|
|
38679
|
-
var cronSchema =
|
|
38778
|
+
var cronSchema = cronExpressionSchema;
|
|
38680
38779
|
var agentTemplateListInputSchema = external_exports.object({}).strict();
|
|
38681
38780
|
var agentTemplateSummarySchema = external_exports.object({
|
|
38682
38781
|
slug: external_exports.string().min(1),
|
|
@@ -38784,6 +38883,11 @@ var agentSetupStateSchema = external_exports.object({
|
|
|
38784
38883
|
steward_seat_id: uuidSchema2.nullable(),
|
|
38785
38884
|
steward_chain_resolves_to_human: external_exports.boolean(),
|
|
38786
38885
|
has_occupancy: external_exports.boolean(),
|
|
38886
|
+
// DER-1529: the seat agent's run-time skill-discovery toggle. VISIBILITY only —
|
|
38887
|
+
// it never grants tools (invariant #6). Always present (the column is NOT NULL
|
|
38888
|
+
// default false) so agent_setup.get / .update round-trip the current value back
|
|
38889
|
+
// to any client; conservative default false (#10).
|
|
38890
|
+
skill_discovery_enabled: external_exports.boolean(),
|
|
38787
38891
|
tool_decisions: external_exports.array(agentSetupToolDecisionSchema),
|
|
38788
38892
|
available_skill_recommendations: external_exports.array(agentSetupSkillRecommendationSchema),
|
|
38789
38893
|
assigned_skills: external_exports.array(skillAssignmentSummarySchema),
|
|
@@ -38815,9 +38919,12 @@ var agentSetupUpdateInputSchema = external_exports.object({
|
|
|
38815
38919
|
// DER-787 (H1): set the structured model selection for this agent.
|
|
38816
38920
|
model_config: agentModelConfigSchema.optional(),
|
|
38817
38921
|
answers: external_exports.array(external_exports.string().min(1).max(2e3)).max(20).optional(),
|
|
38818
|
-
tool_decisions: external_exports.array(agentSetupToolUpdateSchema).max(50).optional()
|
|
38922
|
+
tool_decisions: external_exports.array(agentSetupToolUpdateSchema).max(50).optional(),
|
|
38923
|
+
// DER-1529: toggle run-time skill discovery (visibility only; never grants
|
|
38924
|
+
// tools). Settable any time, including on a live agent.
|
|
38925
|
+
skill_discovery_enabled: external_exports.boolean().optional()
|
|
38819
38926
|
}).strict().refine(
|
|
38820
|
-
(value) => value.parent_seat_id !== void 0 || value.steward_seat_id !== void 0 || value.lane !== void 0 || value.schedule_cron !== void 0 || value.model_config !== void 0 || value.answers !== void 0 || value.tool_decisions !== void 0,
|
|
38927
|
+
(value) => value.parent_seat_id !== void 0 || value.steward_seat_id !== void 0 || value.lane !== void 0 || value.schedule_cron !== void 0 || value.model_config !== void 0 || value.answers !== void 0 || value.tool_decisions !== void 0 || value.skill_discovery_enabled !== void 0,
|
|
38821
38928
|
"Provide at least one field to update."
|
|
38822
38929
|
);
|
|
38823
38930
|
var agentUpdateScheduleInputSchema = external_exports.object({
|
|
@@ -39211,6 +39318,13 @@ var AGENT_POLICY_DEFAULT = {
|
|
|
39211
39318
|
max_autonomous_risk: AGENT_POLICY_PROFILE_CEILINGS.balanced
|
|
39212
39319
|
};
|
|
39213
39320
|
|
|
39321
|
+
// ../../packages/protocol/src/confirmation-policy.ts
|
|
39322
|
+
var CLI_CONFIRMATION_MODES = ["trusted_operator", "careful"];
|
|
39323
|
+
var cliConfirmationModeSchema = external_exports.enum(CLI_CONFIRMATION_MODES);
|
|
39324
|
+
var storedConfirmationPolicySchema = external_exports.object({
|
|
39325
|
+
cli_mode: cliConfirmationModeSchema
|
|
39326
|
+
}).strict();
|
|
39327
|
+
|
|
39214
39328
|
// ../../packages/protocol/src/aicos.ts
|
|
39215
39329
|
var uuidSchema5 = external_exports.string().uuid();
|
|
39216
39330
|
var jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.unknown());
|
|
@@ -39457,17 +39571,38 @@ var AICOS_ATTACHMENT_CONTENT_TYPES = ["image/png", "image/jpeg", "image/webp", "
|
|
|
39457
39571
|
var AICOS_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
39458
39572
|
var AICOS_MAX_ATTACHMENTS_PER_TURN = 4;
|
|
39459
39573
|
var aicosAttachmentContentTypeSchema = external_exports.enum(AICOS_ATTACHMENT_CONTENT_TYPES);
|
|
39574
|
+
var AICOS_ATTACHMENT_READINESS = ["ready", "unavailable", "expired"];
|
|
39575
|
+
var aicosAttachmentReadinessSchema = external_exports.enum(AICOS_ATTACHMENT_READINESS);
|
|
39460
39576
|
var aicosAttachmentSchema = external_exports.object({
|
|
39461
39577
|
id: uuidSchema5,
|
|
39462
39578
|
contentType: aicosAttachmentContentTypeSchema,
|
|
39463
39579
|
sizeBytes: external_exports.number().int().positive(),
|
|
39464
39580
|
width: external_exports.number().int().positive().nullable(),
|
|
39465
39581
|
height: external_exports.number().int().positive().nullable(),
|
|
39466
|
-
filename: external_exports.string().max(255).nullable()
|
|
39582
|
+
filename: external_exports.string().max(255).nullable(),
|
|
39583
|
+
// DER-1470: null/absent ref_status reads as "ready" (usable) for back-compat.
|
|
39584
|
+
readiness: aicosAttachmentReadinessSchema.default("ready")
|
|
39467
39585
|
}).strict();
|
|
39468
39586
|
var aicosAttachmentUploadResultSchema = external_exports.object({
|
|
39469
39587
|
attachment: aicosAttachmentSchema
|
|
39470
39588
|
}).strict();
|
|
39589
|
+
var aicosRunnerAttachmentRefSchema = external_exports.object({
|
|
39590
|
+
id: uuidSchema5,
|
|
39591
|
+
content_type: aicosAttachmentContentTypeSchema,
|
|
39592
|
+
size_bytes: external_exports.number().int().positive(),
|
|
39593
|
+
width: external_exports.number().int().positive().nullable(),
|
|
39594
|
+
height: external_exports.number().int().positive().nullable(),
|
|
39595
|
+
filename: external_exports.string().max(255).nullable(),
|
|
39596
|
+
signed_url: external_exports.string().url().nullable(),
|
|
39597
|
+
signed_url_expires_in_seconds: external_exports.number().int().positive().nullable(),
|
|
39598
|
+
expires_at: external_exports.string().datetime({ offset: true }).nullable(),
|
|
39599
|
+
readiness: aicosAttachmentReadinessSchema
|
|
39600
|
+
}).strict();
|
|
39601
|
+
var AICOS_RUNNER_ATTACHMENT_OUTCOMES = ["used", "expired", "load_failed"];
|
|
39602
|
+
var aicosRunnerAttachmentOutcomeSchema = external_exports.object({
|
|
39603
|
+
id: uuidSchema5,
|
|
39604
|
+
outcome: external_exports.enum(AICOS_RUNNER_ATTACHMENT_OUTCOMES)
|
|
39605
|
+
}).strict();
|
|
39471
39606
|
var aicosMessageSchema = external_exports.object({
|
|
39472
39607
|
id: uuidSchema5,
|
|
39473
39608
|
sessionId: uuidSchema5,
|
|
@@ -39509,6 +39644,7 @@ var aicosAgentBuilderMetadataSchema = external_exports.object({
|
|
|
39509
39644
|
agentBuilder: agentBuilderCompilationSchema
|
|
39510
39645
|
}).strict();
|
|
39511
39646
|
var aicosTurnRequestSchema = external_exports.object({
|
|
39647
|
+
requestId: uuidSchema5,
|
|
39512
39648
|
sessionId: uuidSchema5.nullable().optional(),
|
|
39513
39649
|
purpose: aicosConversationPurposeSchema.default("general"),
|
|
39514
39650
|
// Empty is permitted only when attachments are present (an image-only turn);
|
|
@@ -41176,6 +41312,9 @@ var MCP_ONBOARDING_TOOL_NAMES = {
|
|
|
41176
41312
|
setCompass: `${MCP_TOOL_PREFIX}_set_compass`,
|
|
41177
41313
|
staffSeat: `${MCP_TOOL_PREFIX}_staff_seat`
|
|
41178
41314
|
};
|
|
41315
|
+
var FORGE_TURN_HARD_CEILING_SECONDS = 30 * 60;
|
|
41316
|
+
var FORGE_TURN_PHASE_OVERHEAD_SECONDS = 20 * 60;
|
|
41317
|
+
var SEAT_MCP_CLAIM_TOKEN_TTL_SECONDS = (FORGE_TURN_HARD_CEILING_SECONDS + FORGE_TURN_PHASE_OVERHEAD_SECONDS) * 2;
|
|
41179
41318
|
|
|
41180
41319
|
// ../../packages/protocol/src/mcp-onboarding.ts
|
|
41181
41320
|
var MCP_TOKEN_ABSOLUTE_MAX_EXPIRY_MS = (MCP_TOKEN_NO_EXPIRY_SENTINEL_DAYS + 1) * 24 * 60 * 60 * 1e3;
|
|
@@ -41363,7 +41502,15 @@ var pendingConfirmationSchema = external_exports.object({
|
|
|
41363
41502
|
}).strict()
|
|
41364
41503
|
}).strict();
|
|
41365
41504
|
var confirmationApproveInputSchema = external_exports.object({
|
|
41366
|
-
confirmation_id: external_exports.string().min(1)
|
|
41505
|
+
confirmation_id: external_exports.string().min(1),
|
|
41506
|
+
// DER-1490 — the auditable "reviewed" signal. The CLI sets it true ONLY after
|
|
41507
|
+
// rendering the confirmation gate to (and, at an interactive TTY, getting an
|
|
41508
|
+
// explicit `y` from) the human. Absent = a silent auto-replay; the server refuses
|
|
41509
|
+
// that path under the tenant's `careful` mode, and for `dangerous` risk in either
|
|
41510
|
+
// mode (see requiresReviewSignal). It is an explicit auditable field, not a TTY
|
|
41511
|
+
// heuristic — the server cannot cryptographically distinguish a human `y` from an
|
|
41512
|
+
// agent replaying a live session token (both arrive as source cli, actor.kind user).
|
|
41513
|
+
reviewed: external_exports.boolean().optional()
|
|
41367
41514
|
}).strict();
|
|
41368
41515
|
var confirmationRejectInputSchema = external_exports.object({
|
|
41369
41516
|
confirmation_id: external_exports.string().min(1),
|
|
@@ -41910,7 +42057,7 @@ var fleetDigestAgentSchema = fleetOverviewRowSchema.extend({
|
|
|
41910
42057
|
spend_7d_usd: external_exports.string(),
|
|
41911
42058
|
spend_30d_usd: external_exports.string(),
|
|
41912
42059
|
cost_drift_percent: external_exports.number().int().nullable(),
|
|
41913
|
-
health_status: external_exports.enum(["needs_review", "stale", "setup_gap", "healthy"]),
|
|
42060
|
+
health_status: external_exports.enum(["needs_review", "stale", "setup_gap", "awaiting_data", "healthy"]),
|
|
41914
42061
|
health_reasons: external_exports.array(external_exports.string()),
|
|
41915
42062
|
recent_failed_runs: external_exports.array(fleetDigestRunSchema),
|
|
41916
42063
|
unresolved_errors: external_exports.array(fleetDigestErrorSchema),
|
|
@@ -42141,6 +42288,11 @@ var errorLogSupersedeOutputSchema = external_exports.object({
|
|
|
42141
42288
|
resolved_at: isoDateTime3
|
|
42142
42289
|
}).strict();
|
|
42143
42290
|
|
|
42291
|
+
// ../../packages/protocol/src/run-summary.ts
|
|
42292
|
+
var runSummaryOutputSchema = external_exports.object({
|
|
42293
|
+
summary: external_exports.string().min(1).max(140)
|
|
42294
|
+
});
|
|
42295
|
+
|
|
42144
42296
|
// ../../packages/protocol/src/runner-settings.ts
|
|
42145
42297
|
var uuid8 = external_exports.string().uuid();
|
|
42146
42298
|
var isoDateTime4 = external_exports.string().datetime({ offset: true });
|
|
@@ -42150,6 +42302,7 @@ var runnerExecutionStateSchema = external_exports.enum([
|
|
|
42150
42302
|
"processing",
|
|
42151
42303
|
"idle_unproven",
|
|
42152
42304
|
"queued_unclaimed",
|
|
42305
|
+
"model_account_exhausted",
|
|
42153
42306
|
"missing_runtime",
|
|
42154
42307
|
"not_connected",
|
|
42155
42308
|
"revoked",
|
|
@@ -42165,8 +42318,9 @@ var runnerExecutionReadinessSchema = external_exports.object({
|
|
|
42165
42318
|
recent_completed_at: isoDateTime4.nullable(),
|
|
42166
42319
|
recent_failed_at: isoDateTime4.nullable()
|
|
42167
42320
|
}).strict();
|
|
42321
|
+
var runnerSandboxPostureSchema = external_exports.enum(["guard", "strict", "off", "none", "unknown"]);
|
|
42168
42322
|
var runnerPostureSchema = external_exports.object({
|
|
42169
|
-
sandbox:
|
|
42323
|
+
sandbox: runnerSandboxPostureSchema,
|
|
42170
42324
|
network: external_exports.enum(["allow", "deny", "unknown"]),
|
|
42171
42325
|
capability_set: external_exports.array(external_exports.string())
|
|
42172
42326
|
}).strict();
|
|
@@ -42408,6 +42562,17 @@ var agentPolicyUpdateInputSchema = external_exports.object({
|
|
|
42408
42562
|
});
|
|
42409
42563
|
}
|
|
42410
42564
|
});
|
|
42565
|
+
var confirmationPolicyDtoSchema = external_exports.object({
|
|
42566
|
+
cli_mode: cliConfirmationModeSchema,
|
|
42567
|
+
explicit: external_exports.boolean()
|
|
42568
|
+
}).strict();
|
|
42569
|
+
var confirmationPolicyGetInputSchema = external_exports.object({}).strict();
|
|
42570
|
+
var confirmationPolicyGetOutputSchema = external_exports.object({
|
|
42571
|
+
confirmation_policy: confirmationPolicyDtoSchema
|
|
42572
|
+
}).strict();
|
|
42573
|
+
var confirmationPolicyUpdateInputSchema = external_exports.object({
|
|
42574
|
+
cli_mode: cliConfirmationModeSchema
|
|
42575
|
+
}).strict();
|
|
42411
42576
|
var tenantSpendStatusSchema = external_exports.object({
|
|
42412
42577
|
spend_usd: external_exports.string(),
|
|
42413
42578
|
soft_cap_usd: external_exports.string().nullable(),
|
|
@@ -42903,6 +43068,20 @@ var softwareTerminationBoundSchema = external_exports.object({
|
|
|
42903
43068
|
max_wall_clock_seconds: external_exports.number().int().positive().max(7 * 24 * 60 * 60).optional(),
|
|
42904
43069
|
cost_budget_usd: external_exports.number().nonnegative().max(1e6).optional()
|
|
42905
43070
|
}).strict();
|
|
43071
|
+
var softwarePhaseSchedulerStateSchema = external_exports.object({
|
|
43072
|
+
status: external_exports.enum(["queued", "parked", "not_configured"]),
|
|
43073
|
+
reason: external_exports.enum([
|
|
43074
|
+
"phase_run_not_found",
|
|
43075
|
+
"missing_owning_seat",
|
|
43076
|
+
"missing_runner_agent",
|
|
43077
|
+
"runner_not_paired",
|
|
43078
|
+
"work_order_expired",
|
|
43079
|
+
"runner_offline"
|
|
43080
|
+
]).nullable(),
|
|
43081
|
+
message: external_exports.string(),
|
|
43082
|
+
remediation_href: external_exports.string().nullable(),
|
|
43083
|
+
work_order_id: uuid10.nullable()
|
|
43084
|
+
});
|
|
42906
43085
|
var softwareBuildRequestSummarySchema = external_exports.object({
|
|
42907
43086
|
id: uuid10,
|
|
42908
43087
|
software_project_id: uuid10,
|
|
@@ -42917,7 +43096,8 @@ var softwareBuildRequestSummarySchema = external_exports.object({
|
|
|
42917
43096
|
max_wall_clock_seconds: external_exports.number().int().nullable(),
|
|
42918
43097
|
cost_budget_usd: external_exports.string().nullable(),
|
|
42919
43098
|
iterations_used: external_exports.number().int(),
|
|
42920
|
-
created_at: external_exports.string()
|
|
43099
|
+
created_at: external_exports.string(),
|
|
43100
|
+
scheduler_state: softwarePhaseSchedulerStateSchema.nullable()
|
|
42921
43101
|
});
|
|
42922
43102
|
var softwarePhaseRunSummarySchema = external_exports.object({
|
|
42923
43103
|
id: uuid10,
|
|
@@ -42926,7 +43106,8 @@ var softwarePhaseRunSummarySchema = external_exports.object({
|
|
|
42926
43106
|
attempt_no: external_exports.number().int(),
|
|
42927
43107
|
entered_at: external_exports.string().nullable(),
|
|
42928
43108
|
exited_at: external_exports.string().nullable(),
|
|
42929
|
-
created_at: external_exports.string()
|
|
43109
|
+
created_at: external_exports.string(),
|
|
43110
|
+
scheduler_state: softwarePhaseSchedulerStateSchema.nullable()
|
|
42930
43111
|
});
|
|
42931
43112
|
var softwareGateSummarySchema = external_exports.object({
|
|
42932
43113
|
id: uuid10,
|
|
@@ -43077,7 +43258,8 @@ var softwareRequestCreateOutputSchema = external_exports.object({
|
|
|
43077
43258
|
software_project_id: uuid10,
|
|
43078
43259
|
status: softwareRequestStatusSchema,
|
|
43079
43260
|
current_phase: softwarePhaseSchema,
|
|
43080
|
-
phase_run_id: uuid10
|
|
43261
|
+
phase_run_id: uuid10,
|
|
43262
|
+
scheduler_state: softwarePhaseSchedulerStateSchema
|
|
43081
43263
|
});
|
|
43082
43264
|
var softwareRequestListInputSchema = external_exports.object({
|
|
43083
43265
|
limit: external_exports.number().int().positive().max(200).optional()
|
|
@@ -43955,6 +44137,23 @@ var softwareVercelDeployPreviewOutputSchema = external_exports.object({
|
|
|
43955
44137
|
status: softwareVercelDeploymentStatusSchema
|
|
43956
44138
|
});
|
|
43957
44139
|
|
|
44140
|
+
// ../../packages/protocol/src/software-factory-events.ts
|
|
44141
|
+
var uuid11 = external_exports.string().uuid();
|
|
44142
|
+
var softwareFactoryPhaseRunCompletedEventSchema = external_exports.object({
|
|
44143
|
+
tenantId: uuid11,
|
|
44144
|
+
phaseRunId: uuid11,
|
|
44145
|
+
buildRequestId: uuid11.nullish(),
|
|
44146
|
+
status: external_exports.enum(["passed", "failed"]),
|
|
44147
|
+
idempotencyKey: external_exports.string().min(1).optional()
|
|
44148
|
+
}).strict();
|
|
44149
|
+
var softwareFactoryBudgetExhaustedEventSchema = external_exports.object({
|
|
44150
|
+
tenantId: uuid11,
|
|
44151
|
+
phaseRunId: uuid11,
|
|
44152
|
+
buildRequestId: uuid11,
|
|
44153
|
+
reason: external_exports.string().min(1),
|
|
44154
|
+
idempotencyKey: external_exports.string().min(1).optional()
|
|
44155
|
+
}).strict();
|
|
44156
|
+
|
|
43958
44157
|
// ../../packages/protocol/src/sync-brief.ts
|
|
43959
44158
|
var uuidSchema15 = external_exports.string().uuid();
|
|
43960
44159
|
var dateSchema = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
@@ -44198,6 +44397,13 @@ var issueEventPayloadSchema = external_exports.object({
|
|
|
44198
44397
|
severity: external_exports.enum(["low", "med", "high", "critical"])
|
|
44199
44398
|
}).strict();
|
|
44200
44399
|
|
|
44400
|
+
// ../../packages/protocol/src/held-tool-action.ts
|
|
44401
|
+
var heldToolActionReplaySchema = external_exports.object({
|
|
44402
|
+
tool_name: external_exports.string().min(1),
|
|
44403
|
+
args: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
44404
|
+
run_id: external_exports.string().uuid()
|
|
44405
|
+
}).strict();
|
|
44406
|
+
|
|
44201
44407
|
// ../../packages/protocol/src/operating.ts
|
|
44202
44408
|
var uuidSchema17 = external_exports.string().uuid();
|
|
44203
44409
|
var taskListInputSchema = external_exports.object({}).strict();
|
|
@@ -44363,7 +44569,12 @@ var escalationDecisionOutputSchema = external_exports.object({
|
|
|
44363
44569
|
status: escalationStatusSchema,
|
|
44364
44570
|
decision_id: uuidSchema18,
|
|
44365
44571
|
decided_by: uuidSchema18,
|
|
44366
|
-
issue_id: uuidSchema18.nullable()
|
|
44572
|
+
issue_id: uuidSchema18.nullable(),
|
|
44573
|
+
// DER-1478: true when the resolved escalation carries a durable replay_action
|
|
44574
|
+
// (an approval-gated connector write) that the approvals path should actuate
|
|
44575
|
+
// after this decision is recorded. Omitted (not false) for non-connector
|
|
44576
|
+
// escalations to keep existing callers' output shape unchanged.
|
|
44577
|
+
replay_action_pending: external_exports.boolean().optional()
|
|
44367
44578
|
}).strict();
|
|
44368
44579
|
|
|
44369
44580
|
// ../../packages/protocol/src/index.ts
|
|
@@ -44789,7 +45000,7 @@ The Compass is drafted, then activated by a human through supersession.
|
|
|
44789
45000
|
order: 15,
|
|
44790
45001
|
title: "AICOS chat guide",
|
|
44791
45002
|
summary: "How the AI Chief of Staff chat works in the authenticated app shell and what it can safely do today.",
|
|
44792
|
-
version: "2026-07-
|
|
45003
|
+
version: "2026-07-10.2",
|
|
44793
45004
|
public: true,
|
|
44794
45005
|
audiences: ["human", "in_app_agent"],
|
|
44795
45006
|
stages: ["company_setup", "operating_rhythm"],
|
|
@@ -44857,10 +45068,12 @@ Each turn carries server-normalized page context: route template, current path,
|
|
|
44857
45068
|
|
|
44858
45069
|
The current chat harness shows the effective lane and model. Cloud is the default and remains the enforced lane during onboarding, even if a post-onboarding lane has been selected. After onboarding, an owner or admin can select cloud, runner, or MCP only when the server says that lane is ready. Lane changes update the governed AICOS agent seat and write an append-only audit event; the browser never decides tenant authority or runner/MCP readiness.
|
|
44859
45070
|
|
|
44860
|
-
AICOS also has an explicit brain selection. Cloud can use the included managed allowance or a tenant Anthropic key (BYOK). Runner
|
|
45071
|
+
AICOS also has an explicit brain selection. Cloud can use the included managed allowance or a tenant Anthropic key (BYOK). Runner brain preference still records Claude or Codex, but interactive AICOS runner turns are currently gated to Claude while the Codex path remains unverified. MCP is labeled as the MCP client. Read this selection with \`aicos.brain_settings.get\`; owners can update it with \`aicos.brain_settings.update\` (CLI flags: \`--lane\`, \`--cloud-brain managed|byok\`, \`--runner-brain claude-cli|codex-cli\`). BYOK requires an active tenant Anthropic key. Choosing \`codex-cli\` for AICOS runner mode is currently rejected with a precondition error until the governed interactive runner path is verified. Runner and MCP answers are labeled in the transcript and do not consume the managed cloud allowance. If the selected runner reports that its active local model account has exhausted its allowance, Settings surfaces **Allowance exhausted** before the user sends another AICOS turn so they can switch lanes, switch runner accounts, or keep working manually instead of waiting on a doomed runner round-trip.
|
|
44861
45072
|
|
|
44862
45073
|
Runner mode uses the same AICOS sessions and transcript. Interactive chat turns are tracked separately from scheduled work orders: each user message has one user-owned turn execution with a short deadline, so two users' chat messages cannot collapse into the same scheduled-work slot. Scheduled and Forge runner work still use work orders. Runner claim packets carry ids and execution metadata, not the user's free-text request. After claim, the local runner calls the governed AICOS context-load tool, which re-checks seat scope and returns the bounded transcript, session summary, route context, tenant clock, grounding text, and source counts authorized for that turn. For execute-ready runners, the claim also carries a server-built Seat work contract that names the governed runtime profile, the AICOS context-loader tool, and the Seat's permission manifest; action attempts still route through command guards, tool-call audit, and pending confirmations. The runner treats loaded tenant clock, context, and contract as authoritative instead of substituting its own runtime clock, local files, or self-invented authority. A runner must be paired, execute-ready, and backed by the live AICOS agent before it can be selected.
|
|
44863
45074
|
|
|
45075
|
+
Cloud and runner submissions carry a stable request ID. Distinct requests that compete while a turn is active receive HTTP \`409\` with code \`AICOS_TURN_CONFLICT\`; the losing request writes no partial user message and consumes none of its attachments. Runner acquisition commits its user message, execution, attachment links, queued transcript projection, and session snapshot together, so a failed projection leaves none of them half-written. Retrying the same request ID returns the original turn and does not rerun the model, builder, onboarding, or runner-queue side effects, even if the currently selected lane or runner readiness changed after the original submission.
|
|
45076
|
+
|
|
44864
45077
|
When a runner claims an interactive AICOS turn, the runner reports start and final result back to {{brand}}. The turn row records claim/start/finish timing, terminal reason, model label, and token counts when the runner or cloud path provides them. The server appends exactly one assistant transcript message for the result, links that message to run evidence, and treats duplicate or late result reports as idempotent. If the runner answer includes a validated in-app link, the web panel can surface the same consent-required navigation suggestion once polling sees that the pending turn is terminal. If a queued, claimed, or running interactive turn passes its short deadline before a runner writes back, the turn is marked offline and the transcript receives a terminal assistant message instead of leaving a permanent "queued" placeholder.
|
|
44865
45078
|
|
|
44866
45079
|
While a runner turn is queued, claimed, or running, the chat panel keeps showing the pending state and polls the session until the answer or terminal status arrives. The composer, model picker, and lane picker are disabled during that active turn so a second browser tab or direct API call cannot overlap another runner turn in the same thread. Terminal runner statuses such as failed, offline, or canceled stay visible only while they are still the latest user turn; if the user continues the conversation in another lane, the stale terminal marker is no longer projected at the bottom of the thread.
|
|
@@ -44875,7 +45088,7 @@ You can attach images to a message from the composer. {{brand}} accepts PNG, JPE
|
|
|
44875
45088
|
|
|
44876
45089
|
AICOS can also retrieve bounded summaries from prior sessions owned by the same tenant user and surface them as session-memory read models. Setup-limited users do not retrieve sensitive historical run or company-memory summaries through this path. This is a practical memory seam for AICOS continuity; it is not yet the future Company Brain, and it does not make unaccepted knowledge authoritative.
|
|
44877
45090
|
|
|
44878
|
-
Platform allowance and BYOK state are checked before routing; an exhausted included allowance keeps AICOS in guided mode and points the user toward BYOK, billing, or manual continuation. The chat transcript and Settings surface the exhaustion reason and provide direct paths to add a tenant Anthropic key or review the plan.
|
|
45091
|
+
Platform allowance and BYOK state are checked before routing; an exhausted included allowance keeps AICOS in guided mode and points the user toward BYOK, billing, or manual continuation. The chat transcript and Settings surface the exhaustion reason and provide direct paths to add a tenant Anthropic key or review the plan. Cloud AICOS provider attempts write canonical run evidence and one linked \`llm_calls\` row with source metadata \`aicos_chat\`, prompt version, latency, token usage, cost, key source, and typed outcome. Managed-key turns consult the tenant hard cap before provider execution; BYOK turns never fall back to the managed platform key. Provider timeouts, provider server errors, empty output, and BYOK credential or allowance failures stay visible as terminal failed turns with retry or Settings recovery instead of being converted into a successful generic answer.
|
|
44879
45092
|
|
|
44880
45093
|
During onboarding, the deterministic harness may execute only explicit low-risk draft actions. In the Responsibility Graph step, a user with setup/action authority can ask AICOS to create one named draft seat per turn; the seat stays vacant, is traceable to the AICOS message through artifact sources, and still needs human review before staffing, Charters, manifests, or go-live. Compass and Charter help remains draft guidance and clarifying questions until the live drafting planner lands. AICOS never silently finishes onboarding, approves Compass or Charter content, signs manifests, goes live, stores credentials, changes autonomy policy, or invites external people; those actions remain human-confirmed.
|
|
44881
45094
|
|
|
@@ -44938,7 +45151,7 @@ Treat AICOS as a coordinator over the operating system. It can become more usefu
|
|
|
44938
45151
|
order: 20,
|
|
44939
45152
|
title: "Responsibility Graph playbook",
|
|
44940
45153
|
summary: "How to build a functions-first graph with seats, owners, Stewards, vacancies, and clean authority.",
|
|
44941
|
-
version: "2026-07-
|
|
45154
|
+
version: "2026-07-10.1",
|
|
44942
45155
|
public: true,
|
|
44943
45156
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44944
45157
|
stages: ["graph_design", "staffing"],
|
|
@@ -44982,7 +45195,7 @@ The graph always resolves to a single canonical root seat (Founder & CEO in the
|
|
|
44982
45195
|
|
|
44983
45196
|
AICOS always stays a peer of the root (never a child of its steward) and never appears as an orphan in the unassigned set \u2014 this is intentional and not a data-quality issue.
|
|
44984
45197
|
|
|
44985
|
-
Seats that do not reach the single root through the resolved parent chain are listed as **unassigned** (with \`unassigned: true\` and no incoming edge). Each \`graph.get\` node carries an \`unassigned\` boolean, and the CLI \`rost graph show\` output prints unassigned seats under a separate \`Unassigned\` section so operators can see which parts of the chart need attention. The web canvas parks unassigned seats at the bottom of the chart instead of inventing fake root edges.
|
|
45198
|
+
Seats that do not reach the single root through the resolved parent chain are listed as **unassigned** (with \`unassigned: true\` and no incoming edge). Each \`graph.get\` node carries an \`unassigned\` boolean, and the CLI \`rost graph show\` output prints unassigned seats under a separate \`Unassigned\` section so operators can see which parts of the chart need attention. The web canvas parks unassigned seats at the bottom of the chart instead of inventing fake root edges, and when any exist a floating **Unassigned seats \u2014 needs attention** panel lists them by name; selecting one flies the canvas to that seat so the gap is one click from a fix, not just visible in the baseline row.
|
|
44986
45199
|
|
|
44987
45200
|
## Forge Developer Team seats are pinned
|
|
44988
45201
|
|
|
@@ -45015,7 +45228,9 @@ The graph also carries a mission-control panel. It pulls the same operational st
|
|
|
45015
45228
|
|
|
45016
45229
|
## An agent seat's profile
|
|
45017
45230
|
|
|
45018
|
-
A staffed agent's seat page
|
|
45231
|
+
A staffed non-AICOS agent's seat page uses one profile shell with eight URL-addressable tabs: **Overview**, **Charter**, **Tools & connections**, **Skills**, **Autonomy**, **Signals**, **Activity**, and **Settings**. The hero shows status, Charter purpose, Steward, seat path, lane, model, and last run. The sidebar keeps access, connection, tool/Skill/Signal/escalation/Charter counters, and schedule facts visible while the tabs organize the existing read models and controls. Legacy links such as \`#agent-ops\`, \`#recent-runs\`, and \`#run-<id>\` select the matching Settings or Activity tab. Human seats and the AICOS foundation seat keep their dedicated pages. Members without sensitive-data access see the same shell with explicit unavailable states for run, tool-call, cost, connection-status, and escalation facts; the page never broadens Trust Card access or turns an unavailable read into a confident empty/disconnected claim.
|
|
45232
|
+
|
|
45233
|
+
Agent creation at \`/agents/new\` begins with four doors: **Describe the job** opens the existing in-app builder, **Start from a template** opens the stock-template picker, **Build manually** opens the custom setup path, and **Import** opens an in-app format explainer. Definition-file import is roadmap-only and never opens a bare file picker; operators can instead continue to the existing-agent pairing flow. Existing \`?mode=template|custom|existing\` and \`?seat=\` resume links continue directly to their current flows.
|
|
45019
45234
|
|
|
45020
45235
|
## Review an agent seat's delivered work
|
|
45021
45236
|
|
|
@@ -45334,7 +45549,7 @@ For CLI and MCP setup, read agent-skill-setup-guide before adding or assigning S
|
|
|
45334
45549
|
order: 42,
|
|
45335
45550
|
title: "Design a custom agent",
|
|
45336
45551
|
summary: "How to build a custom agent from operational questions through the Charter Builder, tools, dry run, and go-live without writing prompts.",
|
|
45337
|
-
version: "2026-07-
|
|
45552
|
+
version: "2026-07-10.1",
|
|
45338
45553
|
public: true,
|
|
45339
45554
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
45340
45555
|
stages: ["staffing", "charter_design"],
|
|
@@ -45380,7 +45595,7 @@ Then choose one of three named triggers:
|
|
|
45380
45595
|
- **Scheduled** \u2014 runs on a recurring cadence you pick (every weekday morning, every morning, weekly, hourly). No cron to write.
|
|
45381
45596
|
- **Event** \u2014 runs in response to work routed to it, like a sync or a mention, rather than on a clock.
|
|
45382
45597
|
|
|
45383
|
-
Open **Advanced** for the explicit lane select and a raw cron expression when you need a custom cadence. The same schedule presets appear on the agent's seat page after go-live (Agent operations \u2192 Run schedule).
|
|
45598
|
+
Open **Advanced** for the explicit lane select and a raw cron expression when you need a custom cadence. A raw schedule is a five-field UTC cron \u2014 \`minute hour day-of-month month day-of-week\` \u2014 supporting \`*\`, \`*/N\` steps, \`A-B\` ranges, and comma lists (for example \`0 9 * * 1-5\`). Day-of-week is 0\u20137 where both 0 and 7 mean Sunday. It is validated when you save: an unsupported or out-of-range expression is rejected with a field-level error rather than saved as a schedule that never runs, and the builder previews the next run in UTC. When both day-of-month and day-of-week are set, a run fires only when both match. The same schedule presets appear on the agent's seat page after go-live (Agent operations \u2192 Run schedule).
|
|
45384
45599
|
|
|
45385
45600
|
## Configure tools and credentials
|
|
45386
45601
|
|
|
@@ -45598,7 +45813,7 @@ Use the lower-level commands after health names a finding: \`agent.get_run\` for
|
|
|
45598
45813
|
order: 46,
|
|
45599
45814
|
title: "Tool access and vault",
|
|
45600
45815
|
summary: "How to give agents access to tools without exposing raw credentials or expanding authority by accident.",
|
|
45601
|
-
version: "2026-07-02.
|
|
45816
|
+
version: "2026-07-02.4",
|
|
45602
45817
|
public: true,
|
|
45603
45818
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
45604
45819
|
stages: ["staffing"],
|
|
@@ -45656,7 +45871,7 @@ Google can be connected from Settings once the workspace OAuth app is configured
|
|
|
45656
45871
|
|
|
45657
45872
|
CLI and MCP can inspect connector readiness without seeing secrets: \`integration.list\` / \`rost_list_integrations\` lists connected providers and health metadata, \`integration.status\` / \`rost_get_integration_status\` reads one provider by id or name, and \`integration.test\` / \`rost_test_integration_connection\` runs the installed provider-specific health check. For Google, the test refreshes the vaulted OAuth credential and reads the Gmail profile, then records only account, scope, and health metadata.
|
|
45658
45873
|
|
|
45659
|
-
\`integration.connect_rest\`
|
|
45874
|
+
\`integration.connect_rest\` creates or rotates the supported Baserow REST integration through the credential flow. It stores the token in the vault, persists only endpoint/account metadata plus a tenant-scoped integration row, marks the adapter as \`rest\`, and returns no vault ref or secret. Because the input includes a raw secret, generated CLI argv refuses the command and it is deliberately not exposed as an agent-callable MCP tool; run it from Settings or the command API credential flow instead of shell arguments.
|
|
45660
45875
|
|
|
45661
45876
|
\`integration.readiness\` / \`{{cli}} integration readiness --provider google --json\` / \`rost_check_integration_readiness\` returns the operator setup checklist: app configuration status, tenant connection state, granted scopes, latest test state, the demo-safe partner path, external verification/CASA caveats, handler availability, and the next operator action. The checklist is metadata-only. It never returns access tokens, refresh tokens, client secrets, vault refs, or raw provider responses. Settings deliberately keeps the live Google card narrower: connected account, last successful test, granted capabilities, and connect/reconnect/test actions.
|
|
45662
45877
|
|
|
@@ -45676,7 +45891,7 @@ There is exactly one way to give a connected tool its credential, and it is the
|
|
|
45676
45891
|
|
|
45677
45892
|
- Stage tools on a draft agent: \`agent.configure_tools\` / \`rost_configure_agent_tools\` \u2014 connect or decline proposed tools and stage credential-ingress requests. Pass vault references, never raw secret material.
|
|
45678
45893
|
- Inspect connector readiness: \`integration.list\` / \`rost_list_integrations\`, \`integration.status\` / \`rost_get_integration_status\`, and \`integration.test\` / \`rost_test_integration_connection\` return provider metadata and health only.
|
|
45679
|
-
- Store a secret: \`credential.ingress\`
|
|
45894
|
+
- Store a secret: \`credential.ingress\` (scope: seat) persists only a vault reference. It is \`credential_flow\` \u2014 because it carries a real human-provided secret it runs only through the Settings vault-ingress flow (a human session); generated CLI argv refuses it and it is deliberately not an agent-callable MCP tool. (Because it redacts that secret, its confirmation also shows a high-risk badge \u2014 see the confirmations guide for badge-versus-level.)
|
|
45680
45895
|
- Sign the manifest: \`charter.sign_manifest\` / \`rost_sign_charter_manifest\` requests human confirmation for the seat's permission manifest.
|
|
45681
45896
|
- Mint local access: prefer \`{{cli}} mcp install\` for users; \`mcp_token.create\` is \`human_required\` and returns the token once. List metadata with \`mcp_token.list\` (never token material); revoke with \`mcp_token.revoke\`.
|
|
45682
45897
|
|
|
@@ -45744,7 +45959,7 @@ External connectors are being rolled out provider by provider, conservatively (r
|
|
|
45744
45959
|
order: 48,
|
|
45745
45960
|
title: "CLI and MCP installation guide",
|
|
45746
45961
|
summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
|
|
45747
|
-
version: "2026-07-
|
|
45962
|
+
version: "2026-07-10.3",
|
|
45748
45963
|
public: true,
|
|
45749
45964
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
45750
45965
|
stages: ["company_setup", "staffing"],
|
|
@@ -46105,7 +46320,7 @@ A leaked tenant-admin token can administer the whole tenant, not just one seat.
|
|
|
46105
46320
|
|
|
46106
46321
|
### Storing the Anthropic key and other credentials
|
|
46107
46322
|
|
|
46108
|
-
Storing the tenant model key or any other secret goes through **credential ingress** as a vault reference \u2014 the secret is never pasted into a prompt, config, or log. Use \`rost_save_tenant_anthropic_key\` (\`tenant.anthropic_key.save\`) for the tenant Anthropic key
|
|
46323
|
+
Storing the tenant model key or any other secret goes through **credential ingress** as a vault reference \u2014 the secret is never pasted into a prompt, config, or log. Use \`rost_save_tenant_anthropic_key\` (\`tenant.anthropic_key.save\`) for the tenant Anthropic key; other secrets go through the Settings vault-ingress flow (\`credential.ingress\`), a human-only interactive flow that is deliberately not an agent-callable MCP tool and refuses raw secrets over CLI argv. \`rost_configure_agent_tools\` stages credential *requests* (provider, scope, and label only) but never accepts raw secrets. Credential storage is a gated \`credential_flow\` confirmation that a human approves. For the vault model and the security posture behind this, see the security-model-guide and the tool-access-and-vault guide; for model-bound data, BYOK provider handling, and local-client provider settings, see the ai-model-data-handling-guide; for the confirmation gate, see the confirmations-guide.
|
|
46109
46324
|
|
|
46110
46325
|
## When to use MCP or CLI
|
|
46111
46326
|
|
|
@@ -46210,7 +46425,7 @@ The signed-in app exposes the same Skill command surface at **Skills**, linked f
|
|
|
46210
46425
|
| \`{{cli}} integration list|readiness|status|test\` / credential flow for Baserow REST connect | \`integration.connect_rest\`, \`integration.list\`, \`integration.readiness\`, \`integration.status\`, \`integration.test\` | Create a vault-backed Baserow REST integration through the credential flow, list connector metadata, read setup-readiness, read one connector's health, and run provider-specific tests without exposing credentials. | Tenant-admin for connect; tenant for reads/tests | \`{{cli}} integration readiness --provider google --json\` |
|
|
46211
46426
|
| \`{{cli}} system health\` | \`system.health\` | Read the scoped system-health snapshot across agent runs, unresolved errors, Signals, Cascade setup, work loop, integrations, runners, notifications, and Sync Brief readiness. | Tenant, member, or seat token | \`{{cli}} system health --json\`; \`{{cli}} system health --seat <id> --json\` |
|
|
46212
46427
|
| \`{{cli}} command usage.report|usage.snapshot\` | \`usage.report\`, \`usage.snapshot\` | Read the AI-workforce ROI report (per-period agent turns, token/run cost, human-accepted value, exceptions, approvals) from immutable usage snapshots, and freeze a period's totals into a new snapshot (insert-only, idempotent). | Tenant | \`{{cli}} command usage.report --json\`; \`{{cli}} command usage.snapshot --json\` |
|
|
46213
|
-
| \`{{cli}} settings get|update|product-learning|agent-policy|rename\` | \`settings.get\`, \`settings.update\`, \`settings.product_learning.get\`, \`settings.product_learning.update\`, \`settings.agent_policy.get\`, \`settings.agent_policy.update\`, \`tenant.rename\` | Read tenant settings, update budget caps, read or update product-learning participation, read or set the company autonomy ceiling, and rename the company. Budget, product-learning, and policy updates stop at human confirmation in non-interactive CLI/MCP sessions; company rename is owner-only and human-gated. | Tenant / Tenant-admin for updates | \`{{cli}} settings product-learning get --json\`; \`{{cli}} settings agent-policy get --json\`; \`{{cli}} settings rename --company "Acme Operations"\` |
|
|
46428
|
+
| \`{{cli}} settings get|update|product-learning|agent-policy|confirmation-mode|rename\` | \`settings.get\`, \`settings.update\`, \`settings.product_learning.get\`, \`settings.product_learning.update\`, \`settings.agent_policy.get\`, \`settings.agent_policy.update\`, \`settings.confirmation_policy.get\`, \`settings.confirmation_policy.update\`, \`tenant.rename\` | Read tenant settings, update budget caps, read or update product-learning participation, read or set the company autonomy ceiling, read or set the CLI confirmation mode, and rename the company. Budget, product-learning, and policy updates stop at human confirmation in non-interactive CLI/MCP sessions; company rename is owner-only and human-gated. | Tenant / Tenant-admin for updates | \`{{cli}} settings product-learning get --json\`; \`{{cli}} settings agent-policy get --json\`; \`{{cli}} settings confirmation-mode get --json\`; \`{{cli}} settings rename --company "Acme Operations"\` |
|
|
46214
46429
|
| \`{{cli}} member invite|update|remove\` | \`member.invite\`, \`member.update\`, \`member.remove\` | Manage tenant members; execution requires an owner or admin membership. | Tenant | \`{{cli}} member invite --email ops@example.com --role member\` |
|
|
46215
46430
|
| \`{{cli}} agent templates|create|setup|tools|dry-run|go-live|status|run-now|fleet-digest|get-run|show\` | \`agent_template.list\`, \`agent.create_from_template\`, \`agent.create_custom\`, \`agent_setup.get\`, \`agent_setup.update\`, \`agent.configure_tools\`, \`agent.run_dry_run\`, \`agent.go_live\`, \`agent.status\`, \`agent.run_now\`, \`agent.fleet_digest\`, \`agent.get_run\`, \`agent.show_markdown\` | Run the full agent setup and operation flow: list templates, create a draft from a template or guided custom answers (with \`--model\` and \`--effort\`), read or answer setup state, connect or decline tools, dry-run, go live, run on demand, capture the fleet-health digest, read one run's transcript/error diagnostics, and show a markdown readout. Human CLI dry-runs print the rehearsal transcript and, when present, the per-tool preview labels (\`Would run\`, \`Blocked\`, \`Escalated\`) before go-live. Create and go-live stop at human gates; the dry-run is ungated by human approval but requires a signed manifest first. | Tenant and seat | \`{{cli}} agent fleet-digest --json\` |
|
|
46216
46431
|
| \`{{cli}} tools list\` | \`tool.catalog\` | List the discoverable tool catalog the builder reads (id, scope tiers, credential requirement, access policy, and execution-boundary guidance). | Tenant | \`{{cli}} tools list --json\` |
|
|
@@ -46223,7 +46438,7 @@ The signed-in app exposes the same Skill command surface at **Skills**, linked f
|
|
|
46223
46438
|
| \`{{cli}} deliverable list|get|create|attach\` | \`deliverable.list\`, \`deliverable.get\`, \`deliverable.create\`, \`deliverable.attach\` | List, get, create, or attach agent deliverables for a seat. Deliverables are durable work outputs visible across UI, CLI, and MCP. | Tenant (list/get), Seat (create/attach) | \`{{cli}} deliverable list --seat-id <id> --json\`; \`{{cli}} deliverable create --title "Brief" --kind brief\` |
|
|
46224
46439
|
| \`{{cli}} graph show\` | \`graph.get\` | Print a table view of the Responsibility Graph with explicit \`seat_id\` and \`parent_seat_id\` columns. | Tenant | \`{{cli}} graph show\` |
|
|
46225
46440
|
| \`{{cli}} seat list|get|create|rename|reparent|decommission\` | \`graph.get\`, \`seat.get\`, \`seat.create\`, \`seat.rename\`, \`seat.reparent\`, \`seat.decommission\`, \`seat.decommission_preview\` | Work with seats as first-class CLI primitives. \`seat decommission --dry-run\` previews affected occupancies, agents, Charters, tokens, credentials, work orders, and schedules before the human-gated teardown. | Tenant / Seat for targeted mutations | \`{{cli}} seat list\`; \`{{cli}} seat decommission --seat-id <id> --dry-run\` |
|
|
46226
|
-
| \`{{cli}} forge project-create|project-list|request-create|request-list|request-show|request-pause|request-cancel|request-resume\` | \`software_factory.project.create\`, \`software_factory.project.list\`, \`software_factory.request.create\`, \`software_factory.request.list\`, \`software_factory.request.show\`, \`software_factory.request.pause\`, \`software_factory.request.cancel\`, \`software_factory.request.resume\` | Create/list Forge projects, open/read governed Forge build requests, and control request lifecycle. Project creation plus pause/cancel/resume are tenant-admin, entitlement-gated, and human-gated; reads and request creation require the Forge add-on.
|
|
46441
|
+
| \`{{cli}} forge project-create|project-list|request-create|request-list|request-show|request-pause|request-cancel|request-resume\` | \`software_factory.project.create\`, \`software_factory.project.list\`, \`software_factory.request.create\`, \`software_factory.request.list\`, \`software_factory.request.show\`, \`software_factory.request.pause\`, \`software_factory.request.cancel\`, \`software_factory.request.resume\` | Create/list Forge projects, open/read governed Forge build requests, and control request lifecycle. Request create/list/show return \`scheduler_state\`: \`queued\` when a runner work order exists, \`parked\` when the Forge team is staffed but no paired online runner can claim it, and \`not_configured\` when the runner-lane Forge Seat is missing. Parked and not-configured states link to \`/settings/runners/setup\`; runner pairing or the repair sweep re-enqueues the same pending intake idempotently. Project creation plus pause/cancel/resume are tenant-admin, entitlement-gated, and human-gated; reads and request creation require the Forge add-on. | Tenant / Tenant-admin for create and lifecycle controls | \`{{cli}} forge project-create --name "Leiluna app" --base-branch main\`; \`{{cli}} forge request-create --project <slug-or-name> --title "Add invoice export"\` (accepts \`--software-project-id <id>\` instead of \`--project\` too); \`{{cli}} forge request-pause --build-request-id <id> --reason "Operator hold"\` |
|
|
46227
46442
|
|
|
46228
46443
|
Skills wrapper help:
|
|
46229
46444
|
|
|
@@ -46316,8 +46531,6 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46316
46531
|
| \`rost_skip_charter_draft\` | \`charter.skip\` | Mark a Charter draft as skipped. | Seat or tenant-admin | Call only when a human defers the seat. |
|
|
46317
46532
|
| \`rost_apply_charter_seat_type_recommendation\` | \`charter.apply_seat_type_recommendation\` | Apply a Charter's seat-type recommendation. | Seat or tenant-admin | Call after reviewing the draft. |
|
|
46318
46533
|
| \`rost_sign_charter_manifest\` | \`charter.sign_manifest\` | Request human confirmation for a permission manifest. | Seat or tenant-admin | Use after tool permissions are reviewed. |
|
|
46319
|
-
| \`rost_approve_pending_confirmation\` | \`confirmation.approve\` | Approve a pending confirmation. | Tenant | Call with \`{"confirmation_id":"<confirmation-id>"}\`. |
|
|
46320
|
-
| \`rost_reject_pending_confirmation\` | \`confirmation.reject\` | Reject a pending confirmation. | Tenant | Call with \`{"confirmation_id":"<confirmation-id>"}\`. |
|
|
46321
46534
|
| \`rost_draft_compass\` | \`compass.draft\` | Create a Compass draft. | Tenant | Call with a concise Compass document. |
|
|
46322
46535
|
| \`rost_update_compass_draft\` | \`compass.update_draft\` | Replace a Compass draft document. | Tenant | Call with the \`compass_version_id\` from \`compass.draft\` as \`draft_id\` and the updated document. |
|
|
46323
46536
|
| \`rost_approve_compass_version\` | \`compass.approve_version\` | Activate a Compass version by supersession. | Tenant | Call after human review. |
|
|
@@ -46327,7 +46540,6 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46327
46540
|
| \`rost_assign_user_to_seat\` | \`staffing.assign_user\` | Assign a human user occupancy to a seat. | Seat or tenant-admin | Call with \`seat_id\` and \`user_id\`. |
|
|
46328
46541
|
| \`rost_assign_dry_run_agent_to_seat\` | \`staffing.assign_agent_dry_run\` | Assign an agent occupancy in dry-run mode. | Seat or tenant-admin | Call only after Steward chain is clear. |
|
|
46329
46542
|
| \`rost_staff_seat\` | \`staffing.assign\` | Compatibility helper for human, agent, or hybrid staffing. | Seat or tenant-admin | Call with \`seat_id\` and \`occupant\`. |
|
|
46330
|
-
| \`rost_store_credential\` | \`credential.ingress\` | Store a secret through the vault and persist only a vault reference. | Seat or tenant-admin | Use vault-backed values only. |
|
|
46331
46543
|
| \`rost_go_live\` | \`agent.go_live\` | Promote a dry-run agent seat to live. | Seat or tenant-admin | Call after human approval. |
|
|
46332
46544
|
| \`rost_create_mcp_token\` | \`mcp_token.create\` | Mint a tenant-admin or seat-scoped MCP token. | Tenant | Prefer \`{{cli}} mcp install\` for users. |
|
|
46333
46545
|
| \`rost_revoke_mcp_token\` | \`mcp_token.revoke\` | Revoke an MCP token immediately. | Tenant | Call with \`token_id\`. |
|
|
@@ -46354,7 +46566,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46354
46566
|
| \`rost_create_agent_from_template\` | \`agent.create_from_template\` | Create a draft stock agent and draft Charter from a template (draft-only; occupancy needs a human steward). | Tenant | Call with \`seat_id\` and \`template_slug\`; expect human confirmation. |
|
|
46355
46567
|
| \`rost_start_agent_setup\` | \`agent_setup.start\` | Start an agent setup draft for template, custom, or existing mode. | Tenant | Call with \`mode\` and seat placement. |
|
|
46356
46568
|
| \`rost_get_agent_setup\` | \`agent_setup.get\` | Read agent setup state, blockers, and next action. | Seat or tenant-admin | Call with \`{"setup_id":"<id>"}\`. |
|
|
46357
|
-
| \`rost_update_agent_setup\` | \`agent_setup.update\` | Update parent, steward, lane, schedule, or
|
|
46569
|
+
| \`rost_update_agent_setup\` | \`agent_setup.update\` | Update parent, steward, lane, schedule, answers, or skill discovery on a setup draft. | Tenant | Call with \`setup_id\` and the fields to change. |
|
|
46358
46570
|
| \`rost_update_agent_schedule\` | \`agent.update_schedule\` | Update a draft or live agent's scheduled execution. | Tenant | Call with \`agent_id\` and \`schedule_cron\`; expect human confirmation. |
|
|
46359
46571
|
| \`rost_decommission_agent\` | \`agent.decommission\` | Retire an agent occupancy safely (no-orphan guarded). | Tenant | Call with \`agent_id\`; expect human confirmation. |
|
|
46360
46572
|
| \`rost_pause_agent\` | \`agent.pause\` | Pause a live agent so it stops scheduled and on-demand runs; reversible. | Tenant | Call with \`seat_id\`; expect human confirmation. |
|
|
@@ -46381,7 +46593,6 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46381
46593
|
| \`rost_resolve_error_log\` | \`error_log.resolve\` | Acknowledge or resolve an error log with a required human reason and optional task, issue, or PR link. | Tenant | Call with \`error_log_id\`, \`reason\`, and \`disposition\` \`acknowledged\` or \`resolved\`. |
|
|
46382
46594
|
| \`rost_supersede_error_log\` | \`error_log.supersede\` | Mark an older error log as superseded by a newer in-tenant error log. | Tenant | Call with \`error_log_id\`, \`new_error_log_id\`, and \`reason\`. |
|
|
46383
46595
|
| \`rost_list_integrations\` | \`integration.list\` | List connected integration metadata and latest health state. | Tenant | Call with \`{}\` or \`{"provider":"google"}\`; no secrets or vault refs are returned. |
|
|
46384
|
-
| \`rost_connect_rest_integration\` | \`integration.connect_rest\` | Create or rotate the vault-backed Baserow REST integration for Signal pulls. | Tenant-admin | Use the credential flow with provider \`baserow\`, HTTPS endpoint, scope, secret name, and secret; no vault refs or secrets are returned. |
|
|
46385
46596
|
| \`rost_check_integration_readiness\` | \`integration.readiness\` | Return the connector setup checklist: OAuth env/callback, tenant connection, scopes, latest test state, external verification/CASA, handler availability, and DER-coded next actions. | Tenant | Call with \`{"provider":"google"}\`; metadata only, no secrets or vault refs. |
|
|
46386
46597
|
| \`rost_get_integration_status\` | \`integration.status\` | Read one integration's metadata by provider or integration id. | Tenant | Call with \`{"provider":"google"}\` or \`{"integration_id":"<id>"}\`. |
|
|
46387
46598
|
| \`rost_test_integration_connection\` | \`integration.test\` | Run the provider-specific connection test and update integration health. | Tenant | Call with \`{"provider":"google"}\`; result is bounded metadata only. |
|
|
@@ -46397,8 +46608,10 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46397
46608
|
| \`rost_update_product_learning_policy\` | \`settings.product_learning.update\` | Set product-learning mode. | Tenant-admin | Human-gated; call with \`{"mode":"enabled"}\`, \`{"mode":"disabled"}\`, or \`{"mode":"enterprise_contract"}\`. |
|
|
46398
46609
|
| \`rost_get_company_autonomy_ceiling\` | \`settings.agent_policy.get\` | Read the company autonomy ceiling (Company Guardrails): profile, enforcement, and max_autonomous_risk. | Tenant | Call with \`{}\`; metadata only. |
|
|
46399
46610
|
| \`rost_set_company_autonomy_ceiling\` | \`settings.agent_policy.update\` | Set the company autonomy ceiling. | Tenant-admin | Owner-only, human-gated; call with \`{"profile":"locked_down"}\` (or \`balanced\`/\`high_autonomy\`, or \`{"profile":"custom","max_autonomous_risk":"high"}\`). Enforced by default. |
|
|
46611
|
+
| \`rost_get_cli_confirmation_mode\` | \`settings.confirmation_policy.get\` | Read the CLI confirmation mode: \`careful\` (the strict default \u2014 the CLI cannot silently auto-approve a pending confirmation) or \`trusted_operator\` (an owner opt-in). | Tenant | Call with \`{}\`; metadata only. |
|
|
46612
|
+
| \`rost_set_cli_confirmation_mode\` | \`settings.confirmation_policy.update\` | Set the CLI confirmation mode. | Tenant-admin | Owner-only, human-gated; call with \`{"cli_mode":"trusted_operator"}\` (or \`{"cli_mode":"careful"}\`). Choosing \`trusted_operator\` establishes an ADR-0018 \xA76 standing authorization; a \`dangerous\`-risk confirmation always requires interactive human review in either mode. |
|
|
46400
46613
|
| \`rost_get_aicos_brain_settings\` | \`aicos.brain_settings.get\` | Read AICOS lane and brain labels for cloud managed/BYOK, runner Claude/Codex, and MCP client. | Tenant | Call with \`{}\`; returns enum labels only, never keys, vault refs, or runner secrets. |
|
|
46401
|
-
| \`rost_update_aicos_brain_settings\` | \`aicos.brain_settings.update\` | Update AICOS lane and cloud/runner brain preferences. | Tenant-admin | Owner-only and human-gated; call with \`{"lane":"runner","runner_brain":"
|
|
46614
|
+
| \`rost_update_aicos_brain_settings\` | \`aicos.brain_settings.update\` | Update AICOS lane and cloud/runner brain preferences. | Tenant-admin | Owner-only and human-gated; call with \`{"lane":"runner","runner_brain":"claude-cli"}\` or \`{"cloud_brain":"byok"}\`. BYOK requires an active tenant Anthropic key. For AICOS, selecting \`codex-cli\` is currently rejected until the governed interactive runner path is verified. |
|
|
46402
46615
|
| \`rost_list_signals\` | \`signal.list\` | List measurables with their latest reading and on/off-track state. | Seat or tenant-admin | Call with \`{}\` to find measurable ids. |
|
|
46403
46616
|
| \`rost_get_signal\` | \`signal.get\` | Read a measurable with its full reading history. | Seat or tenant-admin | Call with \`{"measurable_id":"<id>"}\`. |
|
|
46404
46617
|
| \`rost_confirm_signal_reading\` | \`signal.confirm_reading\` | Confirm an unconfirmed reading as human-verified. | Seat or tenant-admin | Humans confirm; call with \`{"reading_id":"<id>"}\`. |
|
|
@@ -46465,13 +46678,13 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46465
46678
|
| \`rost_revoke_skill_from_seat\` | \`skill.revoke_from_seat\` | Human-gated revocation that stops future Skill use without deleting historical activations. | Tenant-admin | Call with \`{"assignment_id":"<assignment-id>"}\`; non-interactive callers receive a confirmation handoff. |
|
|
46466
46679
|
| \`rost_list_model_catalog\` | \`model.catalog\` | List guided model tiers \u2014 recommendations, token prices, cost bands, best-fit work, and model ids for \`--model\`. | Tenant | Call with \`{}\`. |
|
|
46467
46680
|
| \`rost_create_a_forge_project\` | \`software_factory.project.create\` | Create an active Forge software project before binding repositories or opening build requests. Tenant-admin, human-gated, and entitlement-gated. | Tenant-admin | Call with \`{"name":"Leiluna app","slug":"leiluna-app","base_branch":"main"}\`; non-interactive callers receive a confirmation handoff. |
|
|
46468
|
-
| \`rost_create_a_forge_build_request\` | \`software_factory.request.create\` | Open a governed Forge build request against a connected software project; records the request plus its initial intake phase run. Requires the Forge add-on; the title is untrusted display text. | Tenant | Call with \`{"project":"<slug-or-name>","title":"Add invoice export"}\` (or \`{"software_project_id":"<project-id>", ...}\`). |
|
|
46681
|
+
| \`rost_create_a_forge_build_request\` | \`software_factory.request.create\` | Open a governed Forge build request against a connected software project; records the request plus its initial intake phase run and returns \`scheduler_state\` so MCP callers can distinguish queued work from \`parked\` runner setup or \`not_configured\` staffing gaps. Requires the Forge add-on; the title is untrusted display text. | Tenant | Call with \`{"project":"<slug-or-name>","title":"Add invoice export"}\` (or \`{"software_project_id":"<project-id>", ...}\`). |
|
|
46469
46682
|
| \`rost_pause_a_forge_build_request\` | \`software_factory.request.pause\` | Pause an in-flight Forge build request. New runner claims stop immediately; running work halts at the next task checkpoint. Human-gated. | Tenant-admin | Call with \`{"build_request_id":"<request-id>","reason":"Operator hold"}\`; non-interactive callers receive a confirmation handoff. |
|
|
46470
46683
|
| \`rost_cancel_a_forge_build_request\` | \`software_factory.request.cancel\` | Cancel a Forge build request and expire active runner work orders. This is terminal; use pause for reversible holds. Human-gated. | Tenant-admin | Call with \`{"build_request_id":"<request-id>","reason":"Out of scope"}\`; non-interactive callers receive a confirmation handoff. |
|
|
46471
46684
|
| \`rost_resume_a_forge_build_request\` | \`software_factory.request.resume\` | Resume a paused or human-blocked Forge build request from the last completed task checkpoint and requeue the current phase. Human-gated. | Tenant-admin | Call with \`{"build_request_id":"<request-id>","reason":"Budget raised"}\`; non-interactive callers receive a confirmation handoff. |
|
|
46472
46685
|
| \`rost_list_forge_projects\` | \`software_factory.project.list\` | List active Forge software projects so a tenant can choose a project for build requests, GitHub repository bindings, and config. | Tenant | Call with \`{}\`; use the returned \`id\` as \`software_project_id\`. |
|
|
46473
|
-
| \`rost_list_forge_build_requests\` | \`software_factory.request.list\` | The Forge control-room board: build requests with status, current phase, and
|
|
46474
|
-
| \`rost_show_a_forge_build_request\` | \`software_factory.request.show\` | The Forge request detail: the request, current phase, phase-run history, and
|
|
46686
|
+
| \`rost_list_forge_build_requests\` | \`software_factory.request.list\` | The Forge control-room board: build requests with status, current phase, risk, and current \`scheduler_state\` remediation. Read-only; requires the Forge add-on. | Tenant | Call with \`{}\` or \`{"limit":20}\`. |
|
|
46687
|
+
| \`rost_show_a_forge_build_request\` | \`software_factory.request.show\` | The Forge request detail: the request, current phase, phase-run history, gates, and current \`scheduler_state\` remediation. Read-only; requires the Forge add-on. | Tenant | Call with \`{"build_request_id":"<request-id>"}\`. |
|
|
46475
46688
|
| \`rost_advance_a_forge_build_request_phase\` | \`software_factory.phase.advance\` | Advance a build request to its next phase; the server enforces the closed phase state machine and rejects an illegal transition. | Tenant | Call with \`{"build_request_id":"<request-id>","to_phase":"discovery_scoping"}\`. |
|
|
46476
46689
|
| \`rost_decide_a_forge_gate\` | \`software_factory.gate.decide\` | A human approves or rejects a Forge gate; an agent caller routes to \`/approvals\` and can never self-approve. Sensitive gates record a linked human decision. | Tenant | Call with \`{"gate_id":"<gate-id>","decision":"approve"}\`; non-interactive callers receive a confirmation handoff. |
|
|
46477
46690
|
| \`rost_answer_forge_plan_clarifications\` | \`software_factory.request.answer_clarification\` | Write structured human answers to a draft plan's clarifying questions and optionally mark it ready for plan review; answers append to the versioned planning artifact and never widen authority or automation mode. | Tenant | Call with \`{"build_request_id":"<request-id>","answers":[{"question":"Which database?","answer":"Postgres"}],"mark_ready_for_plan_review":true}\`. |
|
|
@@ -46495,7 +46708,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
46495
46708
|
| \`rost_create_a_forge_vercel_preview_deployment\` | \`software_factory.vercel.deploy_preview\` | Create a Vercel preview deployment for a linked project after Forge deploy-preview authority, the Vercel OAuth vault-ref connection, and required preview secret/config grants are verified. | Tenant | Call with \`{"software_project_id":"<project-id>","commit_sha":"<sha>","git_ref":"feature/example","seat_id":"<seat-id>"}\`; returns deployment metadata, not tokens. |
|
|
46496
46709
|
| \`rost_list_forge_runner_capacity\` | \`software_factory.capacity.list\` | List Forge runner capacity observations (advisory scheduling input only, never an authority input). Read-only; requires the Forge add-on. | Tenant | Call with \`{}\` or \`{"runner_id":"<runner-id>"}\`. |
|
|
46497
46710
|
|
|
46498
|
-
The web Forge control room at \`/forge\` uses the same command path for project selection, build request creation/list/detail reads, request pause/cancel/resume controls, runner capacity, configuration status, and Developer Team install. A request detail page shows phase history, open gates, plan progress, loop/time/cost ceilings, and latest plan-review questions from \`software_factory.request.show\`; answering a clarification, approving a plan, rejecting it, pausing, canceling, or resuming calls the same Forge commands exposed over CLI/MCP. Lifecycle controls deliberately stage pending confirmations before changing runner state. The install button also stages a pending confirmation before durable seats, Skills, authority grants, or live agent activation are applied; the install page states plainly that approving activates the 7 agents at a pull-request-only, observe-first posture and surfaces the company autonomy ceiling.
|
|
46711
|
+
The web Forge control room at \`/forge\` uses the same command path for project selection, build request creation/list/detail reads, request pause/cancel/resume controls, runner capacity, configuration status, and Developer Team install. Request cards and detail pages surface \`scheduler_state\` directly: queued work shows its work-order state, parked work explains that no paired online runner is available, and not-configured work sends the operator to \`/settings/runners/setup\`. A request detail page also shows phase history, open gates, plan progress, loop/time/cost ceilings, and latest plan-review questions from \`software_factory.request.show\`; answering a clarification, approving a plan, rejecting it, pausing, canceling, or resuming calls the same Forge commands exposed over CLI/MCP. Lifecycle controls deliberately stage pending confirmations before changing runner state. The install button also stages a pending confirmation before durable seats, Skills, authority grants, or live agent activation are applied; the install page states plainly that approving activates the 7 agents at a pull-request-only, observe-first posture and surfaces the company autonomy ceiling.
|
|
46499
46712
|
|
|
46500
46713
|
#### Forge GitHub App access
|
|
46501
46714
|
|
|
@@ -47095,7 +47308,7 @@ Agents can suggest commitments and report progress. They should not create a new
|
|
|
47095
47308
|
order: 61,
|
|
47096
47309
|
title: "Signal guide",
|
|
47097
47310
|
summary: "How to define and read measurables so the company runs on evidence instead of status theater.",
|
|
47098
|
-
version: "2026-07-
|
|
47311
|
+
version: "2026-07-08.1",
|
|
47099
47312
|
public: true,
|
|
47100
47313
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
47101
47314
|
stages: ["operating_rhythm"],
|
|
@@ -47158,6 +47371,10 @@ Avoid vanity numbers, manual-only status fields, and metrics nobody can act on.
|
|
|
47158
47371
|
|
|
47159
47372
|
A human can run the whole loop from the Signal page. The page opens answer-first \u2014 one computed line ("2 of 14 measurables need attention," or "All 14 measurables on target" when nothing needs you) with only the exception rows fully visible; healthy measurables collapse to a single "N on target" count you can expand. Every create/edit action lives behind the one "+ Add" button, which opens a drawer with four segments: **Log reading** records a human reading (the same \`signal.correct_reading\` path, or a row's own "Log reading" button seeds the drawer to that measurable), **New measurable** creates one against a seat (the \`measurable.create\` path, plus adopting from the template catalog), **Sources**, and **CSV import**. You do not need an agent to keep Signal current.
|
|
47160
47373
|
|
|
47374
|
+
## Per-seat budget alerts
|
|
47375
|
+
|
|
47376
|
+
The Signal page shows a seat-cost strip: each seat's trailing 30-day agent run spend. On top of that you can set a per-seat soft budget alert \u2014 a spend threshold in USD. When a seat's 30-day spend approaches its threshold (at 80%) or crosses it, a visible alert appears in the cost strip on the scorecard. This alert is advisory only: it never blocks a run or pauses an agent (hard budget enforcement is a separate, later capability). Set a threshold from the cost strip by choosing an agent seat and entering an amount, or leave the amount blank to clear it. Thresholds are per tenant and per seat; setting one records a durable event.
|
|
47377
|
+
|
|
47161
47378
|
## Import a scorecard from CSV
|
|
47162
47379
|
|
|
47163
47380
|
To move off a spreadsheet or another operating tool, open the Signal page's "+ Add" drawer and choose the "CSV import" segment (the \`signal.import\` command, also available on CLI/MCP). Each row becomes a measurable owned by the seat whose name matches the row's owner, with its target, unit, direction, cadence, and trailing readings. Import is idempotent \u2014 re-importing upserts by period \u2014 and a row whose cadence conflicts with an existing measurable is reported and skipped, not overwritten.
|
|
@@ -47519,11 +47736,11 @@ Do not claim that a live charge happened unless Stripe test/live evidence proves
|
|
|
47519
47736
|
order: 72,
|
|
47520
47737
|
title: "Settings guide",
|
|
47521
47738
|
summary: "How to use Settings as the control plane for company access, channels, providers, tokens, and operating defaults.",
|
|
47522
|
-
version: "2026-07-
|
|
47739
|
+
version: "2026-07-09.2",
|
|
47523
47740
|
public: true,
|
|
47524
47741
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
47525
47742
|
stages: ["company_setup", "staffing"],
|
|
47526
|
-
relatedCommandIds: ["onboarding.create_invite", "mcp_token.create", "mcp_token.revoke", "mcp_token.list", "integration.list", "integration.readiness", "integration.status", "integration.test", "settings.get", "settings.update", "tenant.rename", "settings.sync_brief_scope.get", "settings.sync_brief_scope.update", "settings.product_learning.get", "settings.product_learning.update", "settings.agent_policy.get", "settings.agent_policy.update", "aicos.brain_settings.get", "aicos.brain_settings.update"],
|
|
47743
|
+
relatedCommandIds: ["onboarding.create_invite", "mcp_token.create", "mcp_token.revoke", "mcp_token.list", "integration.list", "integration.readiness", "integration.status", "integration.test", "settings.get", "settings.update", "tenant.rename", "settings.sync_brief_scope.get", "settings.sync_brief_scope.update", "settings.product_learning.get", "settings.product_learning.update", "settings.agent_policy.get", "settings.agent_policy.update", "settings.confirmation_policy.get", "settings.confirmation_policy.update", "aicos.brain_settings.get", "aicos.brain_settings.update"],
|
|
47527
47744
|
legal: { publicRisk: "low", notes: ["{{brand}}-native settings guidance."] },
|
|
47528
47745
|
sources: [
|
|
47529
47746
|
{
|
|
@@ -47544,7 +47761,7 @@ Start with members and invites, then provider and channel connections, then MCP
|
|
|
47544
47761
|
|
|
47545
47762
|
Settings is reached from the footer gear in the left sidebar (the nav collapse moved it out of the primary item list). It is a left-rail master\u2013detail layout: a grouped rail (General, Team & Access, Billing, AI & Agents, Connections, Advanced) selects one section at a time, shown in the detail pane. The active section is a URL search param, so \`/settings?section=<id>\` is linkable and survives a refresh. The Connections section is the workspace-level provider surface: Google, Slack, QuickBooks, local runners, seat-scoped agent tokens, and stored secrets appear as provider cards with status, used-for chips, actions, and the shared Access language (**Off / Read / Draft / Act with approval**). Detailed management forms for tokens, channels, runners, provider links, and stored vault entries remain reachable from those cards.
|
|
47546
47763
|
|
|
47547
|
-
|
|
47764
|
+
Section state is fully server-side: every in-app link, redirect, and save uses \`?section=<id>\`, never a \`#anchor\` \u2014 there is no client-side hash shim to desync from. Documented shortcut pages redirect straight to the right section instead of a dead end: \`/settings/integrations\` and \`/readiness\` redirect to \`?section=connections\` (connector readiness lives inside the Connections overview, not on a standalone page), \`/settings/members\` redirects to \`?section=members\`, \`/settings/policy\` redirects to \`?section=guardrails\`, and MCP/CLI access tokens live in the Agent access tokens section (not a separate \`/mcp\` page \u2014 \`/mcp\` is the MCP protocol endpoint itself, not a browsable settings screen). The Advanced group links out to \`/migration/ninety\`, the one settings-adjacent page with no home in the grouped rail. Every settings form carries the section it lives in, so saving a change redirects back to that same section with no flash, instead of returning to the top.
|
|
47548
47765
|
|
|
47549
47766
|
## What belongs in Settings
|
|
47550
47767
|
|
|
@@ -47600,7 +47817,7 @@ Agents that run on {{brand}}-managed inference draw against a tenant inference b
|
|
|
47600
47817
|
- Set the hard cap with \`settings.update\` (CLI: \`{{cli}} settings update --hard-cap-usd <amount>\`). The optional soft cap warns before the hard cap and must be less than or equal to it.
|
|
47601
47818
|
- The sandbox dry run is free and is never blocked by the cap, so a fresh company can charter, dry-run, and take an agent live before setting a budget. The cap applies only to real managed-inference runs.
|
|
47602
47819
|
- A company that brings its own provider key (BYOK) is metered on that key and is not subject to the {{brand}}-managed hard cap. BYOK changes the provider account used for eligible cloud calls, not the Charter, tool guard, human gate, or data-retention posture; see the ai-model-data-handling-guide before making provider-handling claims.
|
|
47603
|
-
- AICOS cloud brain selection is managed with \`aicos.brain_settings.get\` and owner-gated \`aicos.brain_settings.update\`. Managed cloud answers draw from the included allowance; BYOK cloud answers meter against the tenant key. Runner Claude
|
|
47820
|
+
- AICOS cloud brain selection is managed with \`aicos.brain_settings.get\` and owner-gated \`aicos.brain_settings.update\`. Managed cloud answers draw from the included allowance; BYOK cloud answers meter against the tenant key. Runner Claude and MCP client answers are labeled separately and do not draw from the managed cloud allowance. The AICOS Codex runner choice remains gated off until the governed interactive runner path is verified.
|
|
47604
47821
|
|
|
47605
47822
|
## Company autonomy ceiling (Company Guardrails)
|
|
47606
47823
|
|
|
@@ -47613,6 +47830,15 @@ The company autonomy ceiling is a single tenant-wide dial that caps how much ANY
|
|
|
47613
47830
|
- An explicitly set ceiling is ENFORCED by default. Enforcement has two modes: \`enforce\` blocks an over-ceiling autonomous call and routes it to the steward (it is never a dead-end deny \u2014 a human can still approve), and \`observe\` allows the call but logs it. The platform default for a company that never set a policy is balanced + observe, so existing live agents are never retro-bricked.
|
|
47614
47831
|
- When enforcement is on, signing a manifest or taking an agent live is REFUSED if the manifest grants always_allow to a tool whose risk exceeds the ceiling; the error names the exact ceiling, tool, and risk in its \`details\`. Lower that tool to always_ask, or raise the ceiling, then retry.
|
|
47615
47832
|
|
|
47833
|
+
## CLI confirmation mode
|
|
47834
|
+
|
|
47835
|
+
The CLI confirmation mode is a separate tenant-wide dial. It does not change what an agent may do (that is the company autonomy ceiling above); it changes whether the {{cli}} CLI tool itself may stand in for a human at an already-gated confirmation.
|
|
47836
|
+
|
|
47837
|
+
- \`careful\` is the strict default for every company, including internal ones. The server refuses the CLI's silent auto-replay of a pending confirmation \u2014 a human must review it before it is approved.
|
|
47838
|
+
- \`trusted_operator\` is a deliberate tenant_admin opt-in (an ADR-0018 \xA76 standing authorization) that lets the CLI silently auto-approve a non-\`dangerous\` confirmation. A \`dangerous\`-risk confirmation always requires interactive human review, in either mode.
|
|
47839
|
+
- Read it with \`settings.confirmation_policy.get\` (CLI: \`{{cli}} settings confirmation-mode get\`). Set it with \`settings.confirmation_policy.update\` (CLI: \`{{cli}} settings confirmation-mode set --mode trusted_operator\`). Owner-only and human-gated.
|
|
47840
|
+
- Stored at \`tenants.settings.confirmation_policy\`; enforced server-side in \`confirmation.approve\`, so a stale or modified local CLI cannot bypass it.
|
|
47841
|
+
|
|
47616
47842
|
## Agent guidance
|
|
47617
47843
|
|
|
47618
47844
|
Agents may explain which setting is needed and why. They should not ask users to paste secrets into chat or tool arguments. When credentials are required, route the user to the vault-backed setup flow.`
|
|
@@ -47662,7 +47888,7 @@ When a user asks to add a person, clarify whether they mean app access, seat occ
|
|
|
47662
47888
|
order: 74,
|
|
47663
47889
|
title: "Notifications guide",
|
|
47664
47890
|
summary: "How {{brand}} should notify humans about decisions, escalations, stale work, and agent boundaries.",
|
|
47665
|
-
version: "2026-
|
|
47891
|
+
version: "2026-07-09.1",
|
|
47666
47892
|
public: true,
|
|
47667
47893
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
47668
47894
|
stages: ["operating_rhythm"],
|
|
@@ -47687,6 +47913,7 @@ Notifications should move decisions to the right human without turning {{brand}}
|
|
|
47687
47913
|
- A Signal is broken or stale.
|
|
47688
47914
|
- A Friction item needs a decision.
|
|
47689
47915
|
- A Sync decision creates a handoff.
|
|
47916
|
+
- A seat's reporting line or parent changes (\`seat.reparent\`, a seat merge, or \`agent_setup.update\`) \u2014 the affected seat's Steward and the tenant owners get an in-app notification, so a structural change is never silent (the DER-1388 incident this closes was only caught because a human happened to notice the org chart looked wrong).
|
|
47690
47917
|
|
|
47691
47918
|
## Diagnose failed deliveries
|
|
47692
47919
|
|
|
@@ -47701,7 +47928,7 @@ Every notification should include the seat, cause, evidence, and requested decis
|
|
|
47701
47928
|
order: 75,
|
|
47702
47929
|
title: "Local runner guide",
|
|
47703
47930
|
summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
|
|
47704
|
-
version: "2026-07-
|
|
47931
|
+
version: "2026-07-10.1",
|
|
47705
47932
|
public: true,
|
|
47706
47933
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
47707
47934
|
stages: ["staffing", "operating_rhythm"],
|
|
@@ -47753,11 +47980,11 @@ The runner bearer secret is stored locally and is never displayed. Each claimed
|
|
|
47753
47980
|
|
|
47754
47981
|
## Interactive AICOS runner turns
|
|
47755
47982
|
|
|
47756
|
-
AICOS runner chat uses the same runner process but not the scheduled work-order queue. The runner claims an \`agent_turn_executions\` item for one user message, starts it, executes the local Claude
|
|
47983
|
+
AICOS runner chat uses the same runner process but not the scheduled work-order queue. The runner claims an \`agent_turn_executions\` item for one user message, starts it, executes the local Claude runtime with bounded AICOS context, then reports the final assistant answer back to {{brand}}. Execute-ready AICOS claims include a server-built Seat work contract: the governed runtime profile, the AICOS context-loader tool, and the Seat permission manifest. Actions invoked from that local runtime still pass through command guards, \`tool_calls\` audit, pending confirmations, human gates, and tenant isolation before anything durable changes. The transcript write-back is server-owned: one terminal result creates one assistant message, links it to run evidence, and records the final turn status, terminal reason, timings, model, and token telemetry when present. Retrying the same final report does not append a second answer. Codex remains unavailable for interactive AICOS runner turns until the governed runner path is verified. If an interactive turn expires while queued, claimed, or running, {{brand}} marks it offline and appends a visible terminal assistant message so the user is not left waiting on a stale queued state.
|
|
47757
47984
|
|
|
47758
47985
|
## Forge scheduler work
|
|
47759
47986
|
|
|
47760
|
-
When the Forge add-on is enabled, Forge phase work uses the same runner work-order lane. A Forge phase with an owning runner-agent Seat is linked to a normal \`work_orders\` row; the runner claims
|
|
47987
|
+
When the Forge add-on is enabled, Forge phase work uses the same runner work-order lane. A Forge phase with an owning runner-agent Seat is linked to a normal \`work_orders\` row only when a paired online runner can claim it; otherwise the phase stays pending with \`scheduler_state.status = "parked"\` and the remediation link points to \`/settings/runners/setup\`. Pairing a runner or the repair sweep re-enqueues the same pending phase idempotently, so operators do not get duplicate intake rows or silent expiring work orders. The runner claims queued work, starts it, reports the result, and the server advances the linked phase. There is no separate local lease store.
|
|
47761
47988
|
|
|
47762
47989
|
A build request decomposes into changesets and tasks tracked as queryable execution state. A changeset is the pull-request boundary and the revert unit (default one per request; additional changesets only on a named split criterion such as risk isolation or a reviewability-size budget). A task is one coding session inside a changeset, carrying its own verification and a per-task checkpoint that is the safe resume and pause boundary. Plan-conformance findings loop a changeset back to implementation; a finding can only be accepted, rather than resolved, through a recorded human decision.
|
|
47763
47990
|
|
|
@@ -48639,7 +48866,7 @@ This worked document **omits** \`unanswered_boundaries\` and \`seat_type_recomme
|
|
|
48639
48866
|
order: 43,
|
|
48640
48867
|
title: "Agent builder guide",
|
|
48641
48868
|
summary: "The full agent setup sequence on the CLI/MCP path \u2014 seat, steward, job, boundaries, tools, credentials, model, schedule, dry-run, go-live \u2014 with the structured model config and access tiers.",
|
|
48642
|
-
version: "2026-
|
|
48869
|
+
version: "2026-07-10.1",
|
|
48643
48870
|
public: true,
|
|
48644
48871
|
audiences: ["cli", "mcp", "in_app_agent"],
|
|
48645
48872
|
stages: ["staffing"],
|
|
@@ -48683,7 +48910,7 @@ Building teams of controlled agent workers is the product's core differentiator.
|
|
|
48683
48910
|
## The full setup sequence
|
|
48684
48911
|
|
|
48685
48912
|
1. **Seat** \u2014 the agent occupies a seat in the Responsibility Graph (a function, not a person). Create or pick the seat first.
|
|
48686
|
-
2. **Steward** \u2014 every agent needs a human steward chain (invariant: no orphan agents).
|
|
48913
|
+
2. **Steward** \u2014 every agent needs a human steward chain (invariant: no orphan agents). When the creating human occupies a qualifying seat, setup defaults the new agent's Steward to that seat and, for an unplaced fresh seat, uses it as the parent. You can still set \`steward_seat_id\` explicitly; an explicit Steward wins. Without a qualifying creator seat or explicit Steward, the occupancy is blocked and \`agent_setup.get\` returns remediation candidates.
|
|
48687
48914
|
3. **Job** \u2014 what the seat owns. On \`agent.create_custom\` this is the operational answers (what it owns, what success looks like, what it must never do alone); for a strong contract, submit the full Charter directly via \`charter.update_draft\` (see the charter-authoring deep-dive).
|
|
48688
48915
|
4. **Boundaries** \u2014 the Charter's \`decision_authority\` (can-do / must-ask / never / escalate), \`escalation_rules\`, and \`budget\`. Conservative by default: send/spend/irreversible actions are approval-gated or escalated.
|
|
48689
48916
|
5. **Tools** \u2014 pick from the discoverable catalog (\`{{cli}} tools list\`); each tool has a default scope tier and access policy. Connect or decline via \`agent.configure_tools\`. Selecting a tool records permission; live handlers execute later only behind the signed manifest, server guard, required credential or binding, and connector-specific approval boundary.
|
|
@@ -48972,6 +49199,13 @@ var approveViaSchema = external_exports.object({
|
|
|
48972
49199
|
var pendingConfirmationSchema2 = external_exports.object({
|
|
48973
49200
|
confirmationId: external_exports.string().min(1),
|
|
48974
49201
|
commandId: external_exports.string().min(1),
|
|
49202
|
+
// DER-1490: carried through so an inline auto-approve can always render the
|
|
49203
|
+
// gate before approving (D4-i). Optional/tolerant — an older/malformed
|
|
49204
|
+
// response still resolves confirmationId/commandId, just without the extra
|
|
49205
|
+
// gate detail.
|
|
49206
|
+
summary: external_exports.string().optional().catch(void 0),
|
|
49207
|
+
riskLevel: external_exports.enum(["normal", "sensitive", "dangerous"]).optional().catch(void 0),
|
|
49208
|
+
diff: external_exports.unknown().optional(),
|
|
48975
49209
|
approveVia: approveViaSchema.optional().catch(void 0)
|
|
48976
49210
|
}).passthrough();
|
|
48977
49211
|
var confirmationApprovalOutputSchema = external_exports.object({
|
|
@@ -49088,16 +49322,22 @@ function brandEnvKey3(suffix) {
|
|
|
49088
49322
|
async function renderMcpInstall(input) {
|
|
49089
49323
|
const stderrLines = [];
|
|
49090
49324
|
const canAutoApprove = input.canAutoApprove ?? false;
|
|
49325
|
+
const reviewed = input.reviewed ?? false;
|
|
49326
|
+
const approval = {
|
|
49327
|
+
canAutoApprove,
|
|
49328
|
+
reviewed,
|
|
49329
|
+
pushStderr: (line) => stderrLines.push(line)
|
|
49330
|
+
};
|
|
49091
49331
|
if (input.options.rotateTokenId === void 0 && input.options.scope === void 0) {
|
|
49092
49332
|
throw new Error(MISSING_SCOPE_MESSAGE);
|
|
49093
49333
|
}
|
|
49094
|
-
const mintScope = input.options.rotateTokenId === void 0 ? { scope: input.options.scope, ...input.options.seatId === void 0 ? {} : { seatId: input.options.seatId } } : await resolveRotationScope(input.client, input.options,
|
|
49095
|
-
const token = await createMcpToken(input.client, mcpTokenCreateBody(mintScope, input.options.expiry),
|
|
49334
|
+
const mintScope = input.options.rotateTokenId === void 0 ? { scope: input.options.scope, ...input.options.seatId === void 0 ? {} : { seatId: input.options.seatId } } : await resolveRotationScope(input.client, input.options, approval);
|
|
49335
|
+
const token = await createMcpToken(input.client, mcpTokenCreateBody(mintScope, input.options.expiry), approval);
|
|
49096
49336
|
let rotationOldRevoked = null;
|
|
49097
49337
|
if (input.options.rotateTokenId !== void 0) {
|
|
49098
49338
|
const oldTokenId = input.options.rotateTokenId;
|
|
49099
49339
|
try {
|
|
49100
|
-
const output = await executeWithConfirmation(input.client, "mcp_token.revoke", { token_id: oldTokenId },
|
|
49340
|
+
const output = await executeWithConfirmation(input.client, "mcp_token.revoke", { token_id: oldTokenId }, approval);
|
|
49101
49341
|
const revoke = mcpTokenRevokeOutputSchema2.safeParse(output);
|
|
49102
49342
|
rotationOldRevoked = revoke.success && revoke.data.revoked === true;
|
|
49103
49343
|
if (!rotationOldRevoked) {
|
|
@@ -49178,9 +49418,9 @@ function mcpTokenCreateBody(mint, expiry) {
|
|
|
49178
49418
|
}
|
|
49179
49419
|
return base;
|
|
49180
49420
|
}
|
|
49181
|
-
async function resolveRotationScope(client, options,
|
|
49421
|
+
async function resolveRotationScope(client, options, approval) {
|
|
49182
49422
|
const oldTokenId = options.rotateTokenId;
|
|
49183
|
-
const listOutput = await executeWithConfirmation(client, "mcp_token.list", { include_revoked: true },
|
|
49423
|
+
const listOutput = await executeWithConfirmation(client, "mcp_token.list", { include_revoked: true }, approval);
|
|
49184
49424
|
const parsed = mcpTokenListOutputSchema2.safeParse(listOutput);
|
|
49185
49425
|
if (!parsed.success) {
|
|
49186
49426
|
throw new Error("Could not read the token list to rotate \u2014 mcp_token.list returned an unexpected shape.");
|
|
@@ -49243,11 +49483,11 @@ function revokeFailureReason(error51) {
|
|
|
49243
49483
|
}
|
|
49244
49484
|
return "unknown error";
|
|
49245
49485
|
}
|
|
49246
|
-
async function createMcpToken(client, body,
|
|
49247
|
-
const output = await executeWithConfirmation(client, "mcp_token.create", body,
|
|
49486
|
+
async function createMcpToken(client, body, approval) {
|
|
49487
|
+
const output = await executeWithConfirmation(client, "mcp_token.create", body, approval);
|
|
49248
49488
|
return mcpTokenOutputSchema.parse(output);
|
|
49249
49489
|
}
|
|
49250
|
-
async function executeWithConfirmation(client, commandId, body,
|
|
49490
|
+
async function executeWithConfirmation(client, commandId, body, approval) {
|
|
49251
49491
|
try {
|
|
49252
49492
|
const result = await client.execute(commandId, body);
|
|
49253
49493
|
return result.output;
|
|
@@ -49256,7 +49496,7 @@ async function executeWithConfirmation(client, commandId, body, canAutoApprove)
|
|
|
49256
49496
|
if (!pending) {
|
|
49257
49497
|
throw error51;
|
|
49258
49498
|
}
|
|
49259
|
-
if (!canAutoApprove) {
|
|
49499
|
+
if (!approval.canAutoApprove) {
|
|
49260
49500
|
throw new PendingConfirmationError({
|
|
49261
49501
|
confirmationId: pending.confirmationId,
|
|
49262
49502
|
commandId: pending.commandId,
|
|
@@ -49264,13 +49504,33 @@ async function executeWithConfirmation(client, commandId, body, canAutoApprove)
|
|
|
49264
49504
|
...pending.approveVia?.webUrl === void 0 ? {} : { approveUrl: pending.approveVia.webUrl }
|
|
49265
49505
|
});
|
|
49266
49506
|
}
|
|
49507
|
+
approval.pushStderr(renderPendingConfirmationGate(pending));
|
|
49267
49508
|
const approved = await client.execute("confirmation.approve", {
|
|
49268
|
-
confirmation_id: pending.confirmationId
|
|
49509
|
+
confirmation_id: pending.confirmationId,
|
|
49510
|
+
// DER-1490: never claim a review that didn't happen — omit the field
|
|
49511
|
+
// entirely rather than sending `reviewed: false` (the server treats
|
|
49512
|
+
// "absent" and "false" the same, but an absent field is honest about there
|
|
49513
|
+
// being no signal either way).
|
|
49514
|
+
...approval.reviewed ? { reviewed: true } : {}
|
|
49269
49515
|
});
|
|
49270
49516
|
const approvalOutput = confirmationApprovalOutputSchema.parse(approved.output);
|
|
49271
49517
|
return approvalOutput.output;
|
|
49272
49518
|
}
|
|
49273
49519
|
}
|
|
49520
|
+
function renderPendingConfirmationGate(pending) {
|
|
49521
|
+
const lines = [`Confirmation required: ${pending.commandId} (${pending.confirmationId})`];
|
|
49522
|
+
if (pending.summary !== void 0) {
|
|
49523
|
+
lines.push(redactForLog(pending.summary));
|
|
49524
|
+
}
|
|
49525
|
+
if (pending.riskLevel !== void 0) {
|
|
49526
|
+
lines.push(`Risk level: ${pending.riskLevel}`);
|
|
49527
|
+
}
|
|
49528
|
+
if (pending.diff !== void 0) {
|
|
49529
|
+
lines.push(`Diff:
|
|
49530
|
+
${redactForLog(pending.diff)}`);
|
|
49531
|
+
}
|
|
49532
|
+
return lines.join("\n");
|
|
49533
|
+
}
|
|
49274
49534
|
function pendingCommandConfirmation(error51, commandId) {
|
|
49275
49535
|
if (!(error51 instanceof CommandClientError) || error51.status !== 409 || !error51.response || error51.response.ok) {
|
|
49276
49536
|
return null;
|
|
@@ -49545,7 +49805,7 @@ var COMMAND_MANIFEST = [
|
|
|
49545
49805
|
{ "id": "agent_setup.get", "namespace": "agent_setup", "action": "get", "title": "Get agent setup", "description": "Read the resumable agent-setup state: mode, parent, steward, template, lane, schedule, tool decisions, dry run, and next action.", "requiredScope": "seat", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": false }, { "name": "setup_id", "flag": "setup-id", "type": "string", "required": false }], "hasComplexInput": false, "help": "Read the resumable setup state: mode, parent, steward, lane, schedule, tool decisions, dry-run blockers, and the next action." },
|
|
49546
49806
|
{ "id": "agent_setup.relane", "namespace": "agent_setup", "action": "relane", "title": "Re-lane a live agent", "description": "Governed re-lane of a live agent: pause, move to the new lane, force a fresh dry-run rehearsal on that lane, then go live. Owner-only and human-gated; records a rationale.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "lane", "flag": "lane", "type": "enum", "required": true, "enumValues": ["cloud", "mcp_session", "runner"] }, { "name": "reason", "flag": "reason", "type": "string", "required": true }], "hasComplexInput": false, "help": "Re-lane a live agent (cloud / runner / mcp_session): it pauses, moves to the new lane, forces a fresh dry-run rehearsal on that lane, then goes live \u2014 owner-only, human-gated, with a required rationale. A runner target needs a paired runner; a failed rehearsal leaves the agent paused on the new lane." },
|
|
49547
49807
|
{ "id": "agent_setup.start", "namespace": "agent_setup", "action": "start", "title": "Start agent setup", "description": "Start (or resume) a draft agent setup for a seat in template, custom, or existing mode.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "mode", "flag": "mode", "type": "enum", "required": true, "enumValues": ["template", "custom", "existing"] }, { "name": "template_slug", "flag": "template-slug", "type": "string", "required": false }], "hasComplexInput": false, "help": "Start a draft agent setup from a template, custom, or existing mode; the setup resumes from the draft agent and Charter, not a fresh start." },
|
|
49548
|
-
{ "id": "agent_setup.update", "namespace": "agent_setup", "action": "update", "title": "Update agent setup", "description": "Update the draft agent's parent, steward, lane, schedule, operational answers, and tool decisions.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "parent_seat_id", "flag": "parent-seat-id", "type": "string", "required": false }, { "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": false }, { "name": "lane", "flag": "lane", "type": "enum", "required": false, "enumValues": ["cloud", "mcp_session", "runner"] }, { "name": "schedule_cron", "flag": "schedule-cron", "type": "string", "required": false }, { "name": "answers", "flag": "answers", "type": "array", "required": false, "itemType": "string" }], "hasComplexInput": true, "help": "Update parent, steward, lane, schedule, answers, or tool decisions on the draft; steward and parent changes keep the no-orphan chain explicit." },
|
|
49808
|
+
{ "id": "agent_setup.update", "namespace": "agent_setup", "action": "update", "title": "Update agent setup", "description": "Update the draft agent's parent, steward, lane, schedule, operational answers, and tool decisions.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "parent_seat_id", "flag": "parent-seat-id", "type": "string", "required": false }, { "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": false }, { "name": "lane", "flag": "lane", "type": "enum", "required": false, "enumValues": ["cloud", "mcp_session", "runner"] }, { "name": "schedule_cron", "flag": "schedule-cron", "type": "string", "required": false }, { "name": "answers", "flag": "answers", "type": "array", "required": false, "itemType": "string" }, { "name": "skill_discovery_enabled", "flag": "skill-discovery-enabled", "type": "boolean", "required": false }], "hasComplexInput": true, "help": "Update parent, steward, lane, schedule, answers, or tool decisions on the draft; steward and parent changes keep the no-orphan chain explicit." },
|
|
49549
49809
|
{ "id": "agent_template.list", "namespace": "agent_template", "action": "list", "title": "List agent templates", "description": "List stock agent templates and their responsibilities, default tools, and dry-run rehearsal.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "List stock agent templates and their default tools and safety boundaries before starting a template setup." },
|
|
49550
49810
|
{ "id": "agent.configure_tools", "namespace": "agent", "action": "configure_tools", "title": "Configure agent tools", "description": "Save tool proposals, declines, and credential-ingress requests for a draft agent. Never stores or echoes secret material; vault refs only.", "requiredScope": "seat", "confirmation": "credential_flow", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }], "hasComplexInput": true, "help": "Connect or decline proposed tools and stage credential-ingress requests; never place raw secrets here \u2014 secrets flow through vault-backed credential.ingress as vault refs only.", "example": { "seat_id": "0190aaaa-aaaa-7aaa-8aaa-aaaaaaaaaaaa", "tool_decisions": [{ "tool": "ap.invoices.read", "decision": "connect" }, { "tool": "email.draft", "decision": "connect" }] } },
|
|
49551
49811
|
{ "id": "agent.create_custom", "namespace": "agent", "action": "create_custom", "title": "Create custom agent", "description": "Create a draft custom agent shell and a draft Charter seed from operational answers (what it owns, success, never-do-alone, steward).", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": false }, { "name": "lane", "flag": "lane", "type": "enum", "required": false, "enumValues": ["cloud", "mcp_session", "runner"] }, { "name": "what_it_owns", "flag": "what-it-owns", "type": "string", "required": false }, { "name": "what_success_looks_like", "flag": "what-success-looks-like", "type": "string", "required": false }, { "name": "what_it_must_never_do_alone", "flag": "what-it-must-never-do-alone", "type": "string", "required": false }], "hasComplexInput": true, "help": "Create a draft custom agent from operational answers (what it owns, success, never-do-alone, steward); the Charter Builder owns the contract and go-live stays human-gated.", "example": { "seat_id": "0190aaaa-aaaa-7aaa-8aaa-aaaaaaaaaaaa", "steward_seat_id": "0190bbbb-bbbb-7bbb-8bbb-bbbbbbbbbbbb", "lane": "cloud", "what_it_owns": "Drafting AP invoice entries from inbound vendor emails for human review.", "what_success_looks_like": "Every inbound invoice is drafted within one business day with the correct GL coding.", "what_it_must_never_do_alone": "Never send payment or approve an invoice without a human sign-off." } },
|
|
@@ -49592,9 +49852,9 @@ var COMMAND_MANIFEST = [
|
|
|
49592
49852
|
{ "id": "compass.set", "namespace": "compass", "action": "set", "title": "Set Compass", "description": "Compatibility command for the DER-626 MCP Compass tool.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "draft_id", "flag": "draft-id", "type": "string", "required": false }, { "name": "approve", "flag": "approve", "type": "boolean", "required": false }], "hasComplexInput": true, "help": "Set Compass drafts in the product's own vocabulary and approve only with explicit human confirmation." },
|
|
49593
49853
|
{ "id": "compass.show_markdown", "namespace": "compass", "action": "show_markdown", "title": "Show Compass as markdown", "description": "Compose the current Compass and its open gaps into a clean, human-skimmable markdown card for review.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Render the current Compass and its open gaps as a clean markdown card to show your human a quick review." },
|
|
49594
49854
|
{ "id": "compass.update_draft", "namespace": "compass", "action": "update_draft", "title": "Update Compass draft", "description": "Replace a draft Compass document.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "draft_id", "flag": "draft-id", "type": "string", "required": true }], "hasComplexInput": true, "help": "Pass the compass_version_id returned by compass.draft as draft_id; update_draft replaces only an unpublished draft in this tenant." },
|
|
49595
|
-
{ "id": "confirmation.approve", "namespace": "confirmation", "action": "approve", "title": "Approve pending confirmation", "description": "Approve an in-flight confirmation and execute the original command as the approving human.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Approve pending confirmations only when the requested durable change is clear to the human owner." },
|
|
49855
|
+
{ "id": "confirmation.approve", "namespace": "confirmation", "action": "approve", "title": "Approve pending confirmation", "description": "Approve an in-flight confirmation and execute the original command as the approving human.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }, { "name": "reviewed", "flag": "reviewed", "type": "boolean", "required": false }], "hasComplexInput": false, "help": "Approve pending confirmations only when the requested durable change is clear to the human owner." },
|
|
49596
49856
|
{ "id": "confirmation.reject", "namespace": "confirmation", "action": "reject", "title": "Reject pending confirmation", "description": "Reject an in-flight confirmation without executing the original command.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }, { "name": "reason", "flag": "reason", "type": "string", "required": false }], "hasComplexInput": false, "help": "Reject pending confirmations when authority, evidence, or human intent is unclear." },
|
|
49597
|
-
{ "id": "credential.ingress", "namespace": "credential", "action": "ingress", "title": "Store credential", "description": "Store a secret through the vault and persist only the credential vault reference.", "requiredScope": "seat", "confirmation": "credential_flow", "exposeOverMcp":
|
|
49857
|
+
{ "id": "credential.ingress", "namespace": "credential", "action": "ingress", "title": "Store credential", "description": "Store a secret through the vault and persist only the credential vault reference.", "requiredScope": "seat", "confirmation": "credential_flow", "exposeOverMcp": false, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": false }, { "name": "provider", "flag": "provider", "type": "string", "required": true }, { "name": "scope_description", "flag": "scope-description", "type": "string", "required": true }, { "name": "secret_name", "flag": "secret-name", "type": "string", "required": true }], "hasComplexInput": false, "secretBlocked": true, "help": "Ingress credentials only through vault-backed flows; never place raw secrets in prompts, logs, or docs." },
|
|
49598
49858
|
{ "id": "deliverable.accept", "namespace": "deliverable", "action": "accept", "title": "Accept deliverable value", "description": "Record a human-confirmed accepted value (USD) on a deliverable. Humans decide; agents cannot self-accept. Re-accepting records a correction as a new event; prior events are never mutated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "deliverable_id", "flag": "deliverable-id", "type": "string", "required": true }, { "name": "impact_value_usd", "flag": "impact-value-usd", "type": "number", "required": true }], "hasComplexInput": false, "help": "Record a human-confirmed accepted value (USD) on a deliverable; owner-only and human-gated. Humans decide \u2014 agents cannot self-accept. Re-accepting records a correction as a new event." },
|
|
49599
49859
|
{ "id": "deliverable.attach", "namespace": "deliverable", "action": "attach", "title": "Attach agent deliverable", "description": "Attach a deliverable to a source run, task, or work order for the acting seat. Source refs are validated server-side.", "requiredScope": "seat", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "run_id", "flag": "run-id", "type": "string", "required": false }, { "name": "task_id", "flag": "task-id", "type": "string", "required": false }, { "name": "work_order_id", "flag": "work-order-id", "type": "string", "required": false }, { "name": "kind", "flag": "kind", "type": "enum", "required": false, "enumValues": ["brief", "work_evidence", "artifact", "report", "other"] }, { "name": "title", "flag": "title", "type": "string", "required": true }, { "name": "summary", "flag": "summary", "type": "string", "required": false }, { "name": "content", "flag": "content", "type": "string", "required": false }], "hasComplexInput": true, "help": "Attach a scrubbed deliverable to a source run, task, or work order that belongs to the acting seat." },
|
|
49600
49860
|
{ "id": "deliverable.create", "namespace": "deliverable", "action": "create", "title": "Create agent deliverable", "description": "Create an agent deliverable for the acting seat. The deliverable is scrubbed for secrets before storage.", "requiredScope": "seat", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "kind", "flag": "kind", "type": "enum", "required": false, "enumValues": ["brief", "work_evidence", "artifact", "report", "other"] }, { "name": "title", "flag": "title", "type": "string", "required": true }, { "name": "summary", "flag": "summary", "type": "string", "required": false }, { "name": "content", "flag": "content", "type": "string", "required": false }, { "name": "delivered_at", "flag": "delivered-at", "type": "string", "required": false }], "hasComplexInput": true, "help": "Create a scrubbed durable work output for the acting seat without making a durable company decision." },
|
|
@@ -49627,7 +49887,7 @@ var COMMAND_MANIFEST = [
|
|
|
49627
49887
|
{ "id": "goal.unbind_measurable", "namespace": "goal", "action": "unbind_measurable", "title": "Unbind a Signal from a Cascade goal", "description": "Remove a measurable's drives_status binding from a goal. The goal keeps its last status; unbinding only stops future signal-driven updates.", "requiredScope": "seat", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "goal_id", "flag": "goal-id", "type": "string", "required": true }, { "name": "measurable_id", "flag": "measurable-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Remove a Signal's drives_status binding from a goal; the goal keeps its last status and stops receiving signal-driven updates." },
|
|
49628
49888
|
{ "id": "goal.update", "namespace": "goal", "action": "update", "title": "Update Cascade goal", "description": "Update a goal's title or definition of done. Topology (parent/seat) and status have dedicated commands.", "requiredScope": "tenant_admin", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "goal_id", "flag": "goal-id", "type": "string", "required": true }, { "name": "title", "flag": "title", "type": "string", "required": false }, { "name": "definition_of_done", "flag": "definition-of-done", "type": "string", "required": false }], "hasComplexInput": false, "help": "Update a goal's title or definition of done; topology and status have dedicated commands." },
|
|
49629
49889
|
{ "id": "graph.get", "namespace": "graph", "action": "get", "title": "Get Responsibility Graph", "description": "Return the tenant's Responsibility Graph: seats, edges, root, occupants, and status rollups.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Read the Responsibility Graph to discover seat ids, parentage, occupants, and status before mutating or routing work." },
|
|
49630
|
-
{ "id": "integration.connect_rest", "namespace": "integration", "action": "connect_rest", "title": "Connect REST integration", "description": "Create or rotate the tenant Baserow REST integration using a vault-backed secret. Stores endpoint metadata only; never returns secrets or vault refs.", "requiredScope": "tenant_admin", "confirmation": "credential_flow", "exposeOverMcp":
|
|
49890
|
+
{ "id": "integration.connect_rest", "namespace": "integration", "action": "connect_rest", "title": "Connect REST integration", "description": "Create or rotate the tenant Baserow REST integration using a vault-backed secret. Stores endpoint metadata only; never returns secrets or vault refs.", "requiredScope": "tenant_admin", "confirmation": "credential_flow", "exposeOverMcp": false, "fields": [{ "name": "provider", "flag": "provider", "type": "enum", "required": true, "enumValues": ["baserow"] }, { "name": "endpoint_url", "flag": "endpoint-url", "type": "string", "required": true }, { "name": "scope_description", "flag": "scope-description", "type": "string", "required": true }, { "name": "secret_name", "flag": "secret-name", "type": "string", "required": true }, { "name": "account_label", "flag": "account-label", "type": "string", "required": false }], "hasComplexInput": false, "secretBlocked": true, "help": "Create or rotate a vault-backed Baserow REST integration for Signal pulls. Use the credential flow; never pass secrets through CLI argv." },
|
|
49631
49891
|
{ "id": "integration.discover", "namespace": "integration", "action": "discover", "title": "Discover a connection for signals", "description": "Read-only introspection of a connected source for signal binding. Returns non-secret adapter metadata and a rest-for-signals recipe skeleton to author against signal.preview. Exposes no secrets.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "integration_id", "flag": "integration-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Read-only introspection of a connected source: returns non-secret metadata + a recipe skeleton to author a signal pull against. No network." },
|
|
49632
49892
|
{ "id": "integration.list", "namespace": "integration", "action": "list", "title": "List integrations", "description": "List tenant integration metadata and health status. Returns account/scopes/capabilities only; never secrets or vault refs.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "provider", "flag": "provider", "type": "string", "required": false }], "hasComplexInput": false, "help": "List connected integrations and their metadata-only health state before enabling a connector-backed tool." },
|
|
49633
49893
|
{ "id": "integration.readiness", "namespace": "integration", "action": "readiness", "title": "Check integration readiness", "description": "Return a safe connector setup checklist for operators and agents. Starts with Google and exposes no secrets.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "provider", "flag": "provider", "type": "enum", "required": false, "enumValues": ["google"] }], "hasComplexInput": false, "help": "Read the connector setup checklist before relying on Google-backed agent work; the checklist names missing config, connection, scopes, external verification, and handler blockers." },
|
|
@@ -49669,6 +49929,8 @@ var COMMAND_MANIFEST = [
|
|
|
49669
49929
|
{ "id": "seat.set_type", "namespace": "seat", "action": "set_type", "title": "Set seat type", "description": "Set a Responsibility Graph seat type.", "requiredScope": "seat", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "seat_type", "flag": "seat-type", "type": "enum", "required": true, "enumValues": ["human", "agent", "hybrid"] }], "hasComplexInput": false, "help": "Seat type should follow the work, risk, measurable, and stewardship model." },
|
|
49670
49930
|
{ "id": "settings.agent_policy.get", "namespace": "settings", "action": "agent_policy.get", "title": "Get company autonomy ceiling", "description": "Read this company's Company Guardrails ceiling (ADR-0007): the profile, enforcement mode, and the most an agent may do autonomously (max_autonomous_risk). Returns metadata only.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Read this company's autonomy ceiling (Company Guardrails, ADR-0007): profile, enforcement mode, and the most an agent may do autonomously." },
|
|
49671
49931
|
{ "id": "settings.agent_policy.update", "namespace": "settings", "action": "agent_policy.update", "title": "Set company autonomy ceiling", "description": "Set the Company Guardrails ceiling (ADR-0007): a named profile (locked_down, balanced, high_autonomy) or custom + max_autonomous_risk. Tenant-admin and human-gated. An explicitly set ceiling is ENFORCED by default; pass enforcement=observe to stage a change without blocking.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "profile", "flag": "profile", "type": "enum", "required": true, "enumValues": ["locked_down", "balanced", "high_autonomy", "custom"] }, { "name": "max_autonomous_risk", "flag": "max-autonomous-risk", "type": "enum", "required": false, "enumValues": ["low", "medium", "high", "critical"] }, { "name": "enforcement", "flag": "enforcement", "type": "enum", "required": false, "enumValues": ["observe", "enforce"] }], "hasComplexInput": false, "help": "Set the company autonomy ceiling: a profile (locked_down, balanced, high_autonomy) or custom + max_autonomous_risk. Owner-only, human-gated, and enforced by default." },
|
|
49932
|
+
{ "id": "settings.confirmation_policy.get", "namespace": "settings", "action": "confirmation_policy.get", "title": "Get CLI confirmation mode", "description": "Read this company's CLI confirmation mode (DER-1490): careful (the strict default \u2014 the CLI cannot silently auto-approve a confirmation) or trusted_operator (an owner opt-in permitting silent auto-approval of non-dangerous confirmations). Returns metadata only.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Read this company's CLI confirmation mode: careful (the strict default \u2014 the CLI cannot silently auto-approve a confirmation) or trusted_operator (an owner opt-in permitting silent auto-approval of non-dangerous confirmations)." },
|
|
49933
|
+
{ "id": "settings.confirmation_policy.update", "namespace": "settings", "action": "confirmation_policy.update", "title": "Set CLI confirmation mode", "description": "Set this company's CLI confirmation mode (DER-1490): careful (strict \u2014 no silent CLI auto-approval) or trusted_operator (owner opt-in permitting silent auto-approval of non-dangerous confirmations; dangerous confirmations always require an interactive review). Owner-only and human-gated: choosing trusted_operator establishes an ADR-0018 \xA76 standing authorization.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "cli_mode", "flag": "cli-mode", "type": "enum", "required": true, "enumValues": ["trusted_operator", "careful"] }], "hasComplexInput": false, "help": "Set the CLI confirmation mode to careful or trusted_operator. Owner-only and human-gated; trusted_operator establishes a standing authorization and dangerous confirmations still require an interactive review." },
|
|
49672
49934
|
{ "id": "settings.get", "namespace": "settings", "action": "get", "title": "Get tenant settings", "description": "Read tenant operating settings: plan, status, spend/budgets, integration status, and notification preferences. Returns metadata only.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Read tenant operating settings: plan, spend, budgets, integration status, and notification preferences. No secrets." },
|
|
49673
49935
|
{ "id": "settings.product_learning.get", "namespace": "settings", "action": "product_learning.get", "title": "Get product-learning policy", "description": "Read whether product/page/recommendation analytics may be recorded for this tenant. Security and audit logs remain enabled in every mode.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Read whether product/page/recommendation analytics are allowed for the tenant; security and audit logs always stay enabled." },
|
|
49674
49936
|
{ "id": "settings.product_learning.update", "namespace": "settings", "action": "product_learning.update", "title": "Update product-learning policy", "description": "Set tenant product-learning participation. Disabled and enterprise-contract modes block generic product analytics writes, but not security/audit logs.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "mode", "flag": "mode", "type": "enum", "required": true, "enumValues": ["enabled", "disabled", "enterprise_contract"] }], "hasComplexInput": false, "help": "Set tenant product-learning mode to enabled, disabled, or enterprise_contract; the change is human-gated." },
|
|
@@ -50646,6 +50908,14 @@ function renderAgentPolicySet(output) {
|
|
|
50646
50908
|
const policy = asRecord(asRecord(output).agent_policy);
|
|
50647
50909
|
return `Set company autonomy ceiling to ${field(policy, "profile")} (max_autonomous_risk=${field(policy, "max_autonomous_risk")}, enforcement=${field(policy, "enforcement")}).`;
|
|
50648
50910
|
}
|
|
50911
|
+
function renderConfirmationModeGet(output) {
|
|
50912
|
+
const policy = asRecord(asRecord(output).confirmation_policy);
|
|
50913
|
+
return `confirmation_policy cli_mode=${field(policy, "cli_mode")} explicit=${field(policy, "explicit")}`;
|
|
50914
|
+
}
|
|
50915
|
+
function renderConfirmationModeSet(output) {
|
|
50916
|
+
const policy = asRecord(asRecord(output).confirmation_policy);
|
|
50917
|
+
return `Updated CLI confirmation mode to ${field(policy, "cli_mode")}.`;
|
|
50918
|
+
}
|
|
50649
50919
|
function renderMemberInvite(output) {
|
|
50650
50920
|
const record2 = asRecord(output);
|
|
50651
50921
|
return `Invited ${field(record2, "email")} as ${field(record2, "role")} (invite ${field(record2, "invite_id")}).`;
|
|
@@ -51209,6 +51479,13 @@ var agentPolicySetHandler = bespoke("settings.agent_policy.update", (parsed) =>
|
|
|
51209
51479
|
enforcement: optionalValue(parsed, "enforcement")
|
|
51210
51480
|
});
|
|
51211
51481
|
}, renderAgentPolicySet);
|
|
51482
|
+
var confirmationModeSetHandler = bespoke("settings.confirmation_policy.update", (parsed) => {
|
|
51483
|
+
const mode = optionalValue(parsed, "mode");
|
|
51484
|
+
if (mode !== "careful" && mode !== "trusted_operator") {
|
|
51485
|
+
throw new UsageError("Provide --mode careful|trusted_operator.");
|
|
51486
|
+
}
|
|
51487
|
+
return { cli_mode: mode };
|
|
51488
|
+
}, renderConfirmationModeSet);
|
|
51212
51489
|
var seatDecommissionHandler = (context, rest) => {
|
|
51213
51490
|
const parsed = parseFlags(rest, /* @__PURE__ */ new Set(["dry-run", "preview", "yes", "y"]));
|
|
51214
51491
|
if (parsed.flags.has("dry-run") || parsed.flags.has("preview")) {
|
|
@@ -51230,6 +51507,13 @@ var settingsAgentPolicyHandler = (context, rest) => dispatch(context, "settings
|
|
|
51230
51507
|
},
|
|
51231
51508
|
set: (ctx, r) => agentPolicySetHandler(ctx, r)
|
|
51232
51509
|
}, `Usage: ${context.binName} settings agent-policy get|set --profile locked_down|balanced|high_autonomy|custom [--max-autonomous-risk <r>] [--enforcement enforce|observe]`);
|
|
51510
|
+
var settingsConfirmationModeHandler = (context, rest) => dispatch(context, "settings confirmation-mode", rest, {
|
|
51511
|
+
get: (ctx, r) => {
|
|
51512
|
+
const parsed = parseFlags(r, /* @__PURE__ */ new Set(["yes", "y"]));
|
|
51513
|
+
return execute(ctx, parsed, "settings.confirmation_policy.get", {}, renderConfirmationModeGet);
|
|
51514
|
+
},
|
|
51515
|
+
set: (ctx, r) => confirmationModeSetHandler(ctx, r)
|
|
51516
|
+
}, `Usage: ${context.binName} settings confirmation-mode get|set --mode careful|trusted_operator`);
|
|
51233
51517
|
var agentSetupHandler = (context, rest) => dispatch(context, "agent setup", rest, {
|
|
51234
51518
|
status: (ctx, r) => {
|
|
51235
51519
|
const parsed = parseFlags(r, /* @__PURE__ */ new Set(["yes", "y"]));
|
|
@@ -51408,6 +51692,7 @@ var GROUP_SPECS = {
|
|
|
51408
51692
|
update: { commandId: "settings.update", handler: settingsUpdateHandler, render: renderSettingsUpdate },
|
|
51409
51693
|
"product-learning": { commandId: "settings.product_learning.get", handler: settingsProductLearningHandler },
|
|
51410
51694
|
"agent-policy": { commandId: "settings.agent_policy.get", handler: settingsAgentPolicyHandler },
|
|
51695
|
+
"confirmation-mode": { commandId: "settings.confirmation_policy.get", handler: settingsConfirmationModeHandler },
|
|
51411
51696
|
rename: { commandId: "tenant.rename", handler: settingsRenameHandler, render: renderTenantRename }
|
|
51412
51697
|
}
|
|
51413
51698
|
},
|
|
@@ -51524,6 +51809,8 @@ var EXTRA_SURFACED = /* @__PURE__ */ new Set([
|
|
|
51524
51809
|
// settings product-learning update
|
|
51525
51810
|
"settings.agent_policy.update",
|
|
51526
51811
|
// settings agent-policy set
|
|
51812
|
+
"settings.confirmation_policy.update",
|
|
51813
|
+
// settings confirmation-mode set
|
|
51527
51814
|
"skill.import_github"
|
|
51528
51815
|
// skills import github
|
|
51529
51816
|
]);
|
|
@@ -51627,7 +51914,7 @@ function operationUsageLines(bin) {
|
|
|
51627
51914
|
`${bin} runner install-service|start|stop|restart|status|logs|uninstall --name <name>`,
|
|
51628
51915
|
`${bin} forge project-create|project-list|request-create|request-list|request-show|request-pause|request-cancel|request-resume`,
|
|
51629
51916
|
`${bin} notification settings|test|errors`,
|
|
51630
|
-
`${bin} settings get|update|product-learning|agent-policy|rename`,
|
|
51917
|
+
`${bin} settings get|update|product-learning|agent-policy|confirmation-mode|rename`,
|
|
51631
51918
|
`${bin} member invite|update|remove`,
|
|
51632
51919
|
`${bin} agent templates|create|setup|tools|dry-run|go-live|status|run-now|fleet-digest|get-run|show`,
|
|
51633
51920
|
`${bin} tools list`,
|
|
@@ -51945,9 +52232,10 @@ async function executeSkillCommand(context, commandId, body, options = {}) {
|
|
|
51945
52232
|
} catch (error51) {
|
|
51946
52233
|
const pending = context.io.interactive === true ? pendingConfirmationFromError(error51) : null;
|
|
51947
52234
|
if (pending && pending.commandId === commandId) {
|
|
52235
|
+
renderPendingConfirmationGate2(context.io, pending);
|
|
51948
52236
|
context.io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the interactive CLI user.
|
|
51949
52237
|
`);
|
|
51950
|
-
const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId });
|
|
52238
|
+
const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId, reviewed: true });
|
|
51951
52239
|
return asRecord2(approved.output).output ?? approved.output;
|
|
51952
52240
|
}
|
|
51953
52241
|
throw error51;
|
|
@@ -52193,7 +52481,30 @@ function pendingConfirmationFromError(error51) {
|
|
|
52193
52481
|
if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
|
|
52194
52482
|
return null;
|
|
52195
52483
|
}
|
|
52196
|
-
|
|
52484
|
+
const summary = typeof details.summary === "string" ? details.summary : void 0;
|
|
52485
|
+
const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
|
|
52486
|
+
return {
|
|
52487
|
+
confirmationId,
|
|
52488
|
+
commandId,
|
|
52489
|
+
...summary === void 0 ? {} : { summary },
|
|
52490
|
+
...riskLevel === void 0 ? {} : { riskLevel },
|
|
52491
|
+
...details.diff === void 0 ? {} : { diff: details.diff }
|
|
52492
|
+
};
|
|
52493
|
+
}
|
|
52494
|
+
function renderPendingConfirmationGate2(io, pending) {
|
|
52495
|
+
if (pending.summary !== void 0) {
|
|
52496
|
+
io.stderr.write(`${redactForLog(pending.summary)}
|
|
52497
|
+
`);
|
|
52498
|
+
}
|
|
52499
|
+
if (pending.riskLevel !== void 0) {
|
|
52500
|
+
io.stderr.write(`Risk level: ${pending.riskLevel}
|
|
52501
|
+
`);
|
|
52502
|
+
}
|
|
52503
|
+
if (pending.diff !== void 0) {
|
|
52504
|
+
io.stderr.write(`Diff:
|
|
52505
|
+
${redactForLog(pending.diff)}
|
|
52506
|
+
`);
|
|
52507
|
+
}
|
|
52197
52508
|
}
|
|
52198
52509
|
|
|
52199
52510
|
// src/doctor.ts
|
|
@@ -52202,12 +52513,286 @@ import { access as access2 } from "node:fs/promises";
|
|
|
52202
52513
|
import { delimiter } from "node:path";
|
|
52203
52514
|
import { promisify as promisify3 } from "node:util";
|
|
52204
52515
|
|
|
52516
|
+
// src/runner-sandbox.ts
|
|
52517
|
+
import { existsSync as existsSync2, realpathSync } from "node:fs";
|
|
52518
|
+
import { readdir, rm as rm2, stat as stat2 } from "node:fs/promises";
|
|
52519
|
+
import { tmpdir } from "node:os";
|
|
52520
|
+
import path3 from "node:path";
|
|
52521
|
+
var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
|
|
52522
|
+
var RUNNER_ASKPASS_DIR = path3.join(tmpdir(), "rost-runner-askpass");
|
|
52523
|
+
var RUNNER_TEMP_PREFIXES = [
|
|
52524
|
+
"rost-runner-mcp-",
|
|
52525
|
+
"rost-runner-sandbox-",
|
|
52526
|
+
"rost-runner-askpass-"
|
|
52527
|
+
];
|
|
52528
|
+
var RUNNER_TEMP_STARTUP_SWEEP_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
52529
|
+
var RUNNER_TEMP_TURN_SWEEP_MAX_AGE_MS = 45 * 60 * 1e3;
|
|
52530
|
+
var SENSITIVE_READ_SUBPATHS = [
|
|
52531
|
+
".ssh",
|
|
52532
|
+
".aws",
|
|
52533
|
+
".gnupg",
|
|
52534
|
+
".config/gcloud",
|
|
52535
|
+
".config/gh",
|
|
52536
|
+
".kube",
|
|
52537
|
+
".docker",
|
|
52538
|
+
".npmrc"
|
|
52539
|
+
];
|
|
52540
|
+
var SENSITIVE_READ_LITERALS = [".netrc"];
|
|
52541
|
+
var SENSITIVE_WRITE_SUBPATHS = [
|
|
52542
|
+
".ssh",
|
|
52543
|
+
".aws",
|
|
52544
|
+
".gnupg",
|
|
52545
|
+
".config/git",
|
|
52546
|
+
"bin",
|
|
52547
|
+
"Library/LaunchAgents"
|
|
52548
|
+
];
|
|
52549
|
+
var SENSITIVE_WRITE_LITERALS = [
|
|
52550
|
+
".gitconfig",
|
|
52551
|
+
".zshrc",
|
|
52552
|
+
".zprofile",
|
|
52553
|
+
".zshenv",
|
|
52554
|
+
".zlogin",
|
|
52555
|
+
".bashrc",
|
|
52556
|
+
".bash_profile",
|
|
52557
|
+
".bash_login",
|
|
52558
|
+
".profile"
|
|
52559
|
+
];
|
|
52560
|
+
function parseSandboxMode(value) {
|
|
52561
|
+
const normalized = (value ?? "auto").trim().toLowerCase();
|
|
52562
|
+
if (normalized === "off" || normalized === "strict" || normalized === "auto" || normalized === "") {
|
|
52563
|
+
return normalized === "" ? "auto" : normalized;
|
|
52564
|
+
}
|
|
52565
|
+
return "auto";
|
|
52566
|
+
}
|
|
52567
|
+
function parseSandboxNetwork(value) {
|
|
52568
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
52569
|
+
if (normalized === "" || normalized === "allow") {
|
|
52570
|
+
return { allowNetwork: true, unrecognized: null };
|
|
52571
|
+
}
|
|
52572
|
+
if (normalized === "deny") {
|
|
52573
|
+
return { allowNetwork: false, unrecognized: null };
|
|
52574
|
+
}
|
|
52575
|
+
return { allowNetwork: true, unrecognized: value ?? "" };
|
|
52576
|
+
}
|
|
52577
|
+
function sbplString(value) {
|
|
52578
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
52579
|
+
}
|
|
52580
|
+
function subpath(value) {
|
|
52581
|
+
return `(subpath ${sbplString(value)})`;
|
|
52582
|
+
}
|
|
52583
|
+
function literal2(value) {
|
|
52584
|
+
return `(literal ${sbplString(value)})`;
|
|
52585
|
+
}
|
|
52586
|
+
var CLAUDE_TEMP_SBPL_REGEX = '(regex #"^/private/tmp/claude-[^/]+")';
|
|
52587
|
+
var STRICT_SYSTEM_READ_SUBPATHS = [
|
|
52588
|
+
"/usr",
|
|
52589
|
+
"/bin",
|
|
52590
|
+
"/sbin",
|
|
52591
|
+
"/System",
|
|
52592
|
+
"/Library",
|
|
52593
|
+
"/private/etc",
|
|
52594
|
+
"/private/var/db",
|
|
52595
|
+
"/dev",
|
|
52596
|
+
"/opt",
|
|
52597
|
+
"/nix"
|
|
52598
|
+
];
|
|
52599
|
+
function buildSeatbeltProfile(input) {
|
|
52600
|
+
const home = input.homeDir.replace(/\/+$/, "");
|
|
52601
|
+
const lines = ["(version 1)"];
|
|
52602
|
+
const denyReadBaseSubpaths = input.denyReadPaths.filter((p) => p.endsWith("/")).map((p) => subpath(p.replace(/\/+$/, "")));
|
|
52603
|
+
const denyReadLiterals = input.denyReadPaths.filter((p) => !p.endsWith("/")).map(literal2);
|
|
52604
|
+
const denyWriteSubpaths = input.denyWritePaths.filter((p) => p.endsWith("/")).map((p) => subpath(p.replace(/\/+$/, "")));
|
|
52605
|
+
const denyWriteLiterals = input.denyWritePaths.filter((p) => !p.endsWith("/")).map(literal2);
|
|
52606
|
+
const credentialReadDenies = [
|
|
52607
|
+
...SENSITIVE_READ_SUBPATHS.map((rel) => subpath(`${home}/${rel}`)),
|
|
52608
|
+
...SENSITIVE_READ_LITERALS.map((rel) => literal2(`${home}/${rel}`))
|
|
52609
|
+
];
|
|
52610
|
+
if (input.strict) {
|
|
52611
|
+
lines.push("(deny default)");
|
|
52612
|
+
lines.push("(allow process*)");
|
|
52613
|
+
lines.push("(allow sysctl-read)");
|
|
52614
|
+
lines.push("(allow mach-lookup)");
|
|
52615
|
+
lines.push("(allow iokit-open)");
|
|
52616
|
+
lines.push("(allow system-socket)");
|
|
52617
|
+
lines.push("(allow signal (target self))");
|
|
52618
|
+
lines.push("(allow file-read-metadata)");
|
|
52619
|
+
const strictReads = [
|
|
52620
|
+
// The root directory NODE itself (not its children — `literal`, never `subpath`).
|
|
52621
|
+
// dyld resolves the `/System/Volumes/Preboot/Cryptexes` firmlink to load the arm64
|
|
52622
|
+
// shared cache at process start, and that resolution reads `/`; without this a
|
|
52623
|
+
// deny-default profile SIGABRTs every spawned runtime (cat/node/claude) before it
|
|
52624
|
+
// runs. `(literal "/")` grants only the root dir listing/metadata (no secret), while
|
|
52625
|
+
// every child path stays individually deny-gated. (Guard never hits this branch.)
|
|
52626
|
+
literal2("/"),
|
|
52627
|
+
...STRICT_SYSTEM_READ_SUBPATHS.map(subpath),
|
|
52628
|
+
subpath(`${home}/.claude`),
|
|
52629
|
+
subpath(`${home}/.codex`),
|
|
52630
|
+
subpath(input.tmpDir)
|
|
52631
|
+
];
|
|
52632
|
+
lines.push(`(allow file-read* ${strictReads.join(" ")})`);
|
|
52633
|
+
} else {
|
|
52634
|
+
lines.push("(allow default)");
|
|
52635
|
+
}
|
|
52636
|
+
lines.push("(deny appleevent-send)");
|
|
52637
|
+
if (denyReadBaseSubpaths.length > 0) {
|
|
52638
|
+
lines.push(`(deny file-read* ${denyReadBaseSubpaths.join(" ")})`);
|
|
52639
|
+
}
|
|
52640
|
+
const workspaceReads = [subpath(input.workspaceDir), ...input.allowWritePaths.map(subpath), CLAUDE_TEMP_SBPL_REGEX];
|
|
52641
|
+
lines.push(`(allow file-read* ${workspaceReads.join(" ")})`);
|
|
52642
|
+
lines.push("(deny file-write*)");
|
|
52643
|
+
const allowWrites = [
|
|
52644
|
+
subpath(input.workspaceDir),
|
|
52645
|
+
subpath(input.tmpDir),
|
|
52646
|
+
subpath(`${home}/.claude`),
|
|
52647
|
+
subpath(`${home}/.codex`),
|
|
52648
|
+
...input.allowWritePaths.map(subpath),
|
|
52649
|
+
CLAUDE_TEMP_SBPL_REGEX,
|
|
52650
|
+
// DER-1398: Claude Code's per-session + per-command temp dirs.
|
|
52651
|
+
subpath("/dev/fd"),
|
|
52652
|
+
literal2("/dev/null"),
|
|
52653
|
+
literal2("/dev/stdout"),
|
|
52654
|
+
literal2("/dev/stderr"),
|
|
52655
|
+
literal2("/dev/tty"),
|
|
52656
|
+
literal2("/dev/dtracehelper")
|
|
52657
|
+
];
|
|
52658
|
+
lines.push(`(allow file-write* ${allowWrites.join(" ")})`);
|
|
52659
|
+
const lastReadDenies = [
|
|
52660
|
+
...credentialReadDenies,
|
|
52661
|
+
...denyReadLiterals,
|
|
52662
|
+
...input.denyReadLastSubpaths.map((p) => subpath(p.replace(/\/+$/, "")))
|
|
52663
|
+
];
|
|
52664
|
+
lines.push(`(deny file-read* ${lastReadDenies.join(" ")})`);
|
|
52665
|
+
const denyWrites = [
|
|
52666
|
+
...SENSITIVE_WRITE_SUBPATHS.map((rel) => subpath(`${home}/${rel}`)),
|
|
52667
|
+
...SENSITIVE_WRITE_LITERALS.map((rel) => literal2(`${home}/${rel}`)),
|
|
52668
|
+
// Caller-supplied write-denies (e.g. `<baseRepo>/.git/hooks` + `.git/config`) last so
|
|
52669
|
+
// they override the allowWritePaths re-allow of the shared object store above, while
|
|
52670
|
+
// `.git/{objects,refs,worktrees,logs,index,HEAD}` stay writable for local commits.
|
|
52671
|
+
...denyWriteSubpaths,
|
|
52672
|
+
...denyWriteLiterals
|
|
52673
|
+
];
|
|
52674
|
+
lines.push(`(deny file-write* ${denyWrites.join(" ")})`);
|
|
52675
|
+
lines.push('(deny mach-lookup (global-name "com.apple.coreservices.appleevents"))');
|
|
52676
|
+
if (!input.allowNetwork) {
|
|
52677
|
+
lines.push("(deny network*)");
|
|
52678
|
+
} else if (input.strict) {
|
|
52679
|
+
lines.push("(allow network*)");
|
|
52680
|
+
}
|
|
52681
|
+
return `${lines.join("\n")}
|
|
52682
|
+
`;
|
|
52683
|
+
}
|
|
52684
|
+
function canonical(p) {
|
|
52685
|
+
try {
|
|
52686
|
+
return realpathSync(p);
|
|
52687
|
+
} catch {
|
|
52688
|
+
return p;
|
|
52689
|
+
}
|
|
52690
|
+
}
|
|
52691
|
+
function canonicalFile(p) {
|
|
52692
|
+
return path3.join(canonical(path3.dirname(p)), path3.basename(p));
|
|
52693
|
+
}
|
|
52694
|
+
function isSandboxExecAvailable() {
|
|
52695
|
+
try {
|
|
52696
|
+
return existsSync2(SANDBOX_EXEC_PATH);
|
|
52697
|
+
} catch {
|
|
52698
|
+
return false;
|
|
52699
|
+
}
|
|
52700
|
+
}
|
|
52701
|
+
function resolveSandboxSpec(options) {
|
|
52702
|
+
if (options.mode === "off") {
|
|
52703
|
+
return { kind: "none", reason: "RUNNER_SANDBOX=off (loosened fallback - OS confinement disabled)" };
|
|
52704
|
+
}
|
|
52705
|
+
if (options.platform !== "darwin") {
|
|
52706
|
+
return { kind: "none", reason: `no OS sandbox on ${options.platform} (seatbelt is macOS-only; Linux confinement is a follow-up)` };
|
|
52707
|
+
}
|
|
52708
|
+
const available = options.sandboxExecAvailable ?? isSandboxExecAvailable();
|
|
52709
|
+
if (!available) {
|
|
52710
|
+
return { kind: "none", reason: `sandbox-exec not found at ${SANDBOX_EXEC_PATH}` };
|
|
52711
|
+
}
|
|
52712
|
+
const workspaceDir = canonical(options.workspaceDir);
|
|
52713
|
+
const tmpDir = canonical(options.tmpDir);
|
|
52714
|
+
const homeDir = canonical(options.homeDir);
|
|
52715
|
+
const allowNetwork = options.allowNetwork ?? true;
|
|
52716
|
+
const denyReadPaths = [canonicalFile(options.stateFile)];
|
|
52717
|
+
if (options.runnerBaseDir) {
|
|
52718
|
+
denyReadPaths.push(`${canonical(options.runnerBaseDir).replace(/\/+$/, "")}/`);
|
|
52719
|
+
}
|
|
52720
|
+
const denyWritePaths = (options.denyWritePaths ?? []).map(
|
|
52721
|
+
(p) => p.endsWith("/") ? `${canonical(p.replace(/\/+$/, ""))}/` : canonicalFile(p)
|
|
52722
|
+
);
|
|
52723
|
+
denyWritePaths.push(`${canonicalFile(RUNNER_ASKPASS_DIR)}/`);
|
|
52724
|
+
const allowWritePaths = (options.allowWritePaths ?? []).map(canonical);
|
|
52725
|
+
const denyReadLastSubpaths = [`${canonicalFile(RUNNER_ASKPASS_DIR)}/`];
|
|
52726
|
+
const profile = buildSeatbeltProfile({
|
|
52727
|
+
workspaceDir,
|
|
52728
|
+
tmpDir,
|
|
52729
|
+
homeDir,
|
|
52730
|
+
denyReadPaths,
|
|
52731
|
+
denyReadLastSubpaths,
|
|
52732
|
+
allowWritePaths,
|
|
52733
|
+
denyWritePaths,
|
|
52734
|
+
allowNetwork,
|
|
52735
|
+
strict: options.mode === "strict"
|
|
52736
|
+
});
|
|
52737
|
+
return {
|
|
52738
|
+
kind: "seatbelt",
|
|
52739
|
+
reason: options.mode === "strict" ? "seatbelt (strict: deny-default reads + write confinement)" : "seatbelt (guard: deny sensitive reads/writes)",
|
|
52740
|
+
profile,
|
|
52741
|
+
allowNetwork
|
|
52742
|
+
};
|
|
52743
|
+
}
|
|
52744
|
+
function forgeBuildUnsupportedReason(platform2) {
|
|
52745
|
+
if (platform2 === "darwin") {
|
|
52746
|
+
return null;
|
|
52747
|
+
}
|
|
52748
|
+
return `Forge build execution is unsupported on this host: no macOS seatbelt (sandbox-exec) confinement is available on '${platform2}', so this runner cannot prove filesystem/egress isolation for an untrusted build turn. The server will not offer Forge-phase work to this runner \u2014 unless its tenant is on the narrow, operator-established trust allowlist (FORGE_SANDBOX_TRUSTED_TENANT_IDS) \u2014 until real Linux confinement (namespaces/Landlock, tracked as a DER-1343 follow-up) ships.`;
|
|
52749
|
+
}
|
|
52750
|
+
function wrapCommandWithSandbox(spec, command, args, profilePath) {
|
|
52751
|
+
if (spec.kind === "none" || profilePath === null) {
|
|
52752
|
+
return { command, args, profilePath: null };
|
|
52753
|
+
}
|
|
52754
|
+
return {
|
|
52755
|
+
command: SANDBOX_EXEC_PATH,
|
|
52756
|
+
args: ["-f", profilePath, command, ...args],
|
|
52757
|
+
profilePath
|
|
52758
|
+
};
|
|
52759
|
+
}
|
|
52760
|
+
async function sweepStaleRunnerTempFiles(dir, options = {}) {
|
|
52761
|
+
const maxAgeMs = options.maxAgeMs ?? RUNNER_TEMP_STARTUP_SWEEP_MAX_AGE_MS;
|
|
52762
|
+
const now = options.now ?? Date.now();
|
|
52763
|
+
let entries;
|
|
52764
|
+
try {
|
|
52765
|
+
entries = await readdir(dir);
|
|
52766
|
+
} catch {
|
|
52767
|
+
return [];
|
|
52768
|
+
}
|
|
52769
|
+
const removed = [];
|
|
52770
|
+
await Promise.all(
|
|
52771
|
+
entries.map(async (name) => {
|
|
52772
|
+
if (!RUNNER_TEMP_PREFIXES.some((prefix) => name.startsWith(prefix))) {
|
|
52773
|
+
return;
|
|
52774
|
+
}
|
|
52775
|
+
const full = path3.join(dir, name);
|
|
52776
|
+
try {
|
|
52777
|
+
const info = await stat2(full);
|
|
52778
|
+
if (now - info.mtimeMs < maxAgeMs) {
|
|
52779
|
+
return;
|
|
52780
|
+
}
|
|
52781
|
+
await rm2(full, { recursive: true, force: true });
|
|
52782
|
+
removed.push(name);
|
|
52783
|
+
} catch {
|
|
52784
|
+
}
|
|
52785
|
+
})
|
|
52786
|
+
);
|
|
52787
|
+
return removed;
|
|
52788
|
+
}
|
|
52789
|
+
|
|
52205
52790
|
// src/runner-service.ts
|
|
52206
52791
|
import { execFile as execFileCallback2 } from "node:child_process";
|
|
52207
|
-
import { realpathSync } from "node:fs";
|
|
52208
|
-
import { access, mkdir as mkdir2, readFile as readFile3, readdir, rm as
|
|
52792
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
52793
|
+
import { access, mkdir as mkdir2, readFile as readFile3, readdir as readdir2, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
52209
52794
|
import { homedir as homedir2 } from "node:os";
|
|
52210
|
-
import
|
|
52795
|
+
import path4 from "node:path";
|
|
52211
52796
|
import { promisify as promisify2 } from "node:util";
|
|
52212
52797
|
var execFile2 = promisify2(execFileCallback2);
|
|
52213
52798
|
var SERVICE_VERBS = ["install-service", "start", "stop", "restart", "status", "logs", "uninstall"];
|
|
@@ -52224,9 +52809,9 @@ function isRunnerServiceInvocation(args) {
|
|
|
52224
52809
|
function runnerServicePaths(homeDir, runnerName) {
|
|
52225
52810
|
const safeName = safeRunnerName(runnerName);
|
|
52226
52811
|
return {
|
|
52227
|
-
launchAgentPath:
|
|
52228
|
-
stateFile:
|
|
52229
|
-
logDir:
|
|
52812
|
+
launchAgentPath: path4.join(homeDir, "Library", "LaunchAgents", `${serviceLabel(safeName)}.plist`),
|
|
52813
|
+
stateFile: path4.join(homeDir, "Library", "Application Support", cliBrand.name, `runner-${safeName}.json`),
|
|
52814
|
+
logDir: path4.join(homeDir, "Library", "Logs", cliBrand.name, "runner", safeName)
|
|
52230
52815
|
};
|
|
52231
52816
|
}
|
|
52232
52817
|
function renderLaunchdPlist(config2) {
|
|
@@ -52268,9 +52853,9 @@ ${config2.allowDevelopmentAppUrl === true ? ` <key>${allowInsecureAppUrlEnv}<
|
|
|
52268
52853
|
<key>RunAtLoad</key>
|
|
52269
52854
|
<true/>
|
|
52270
52855
|
<key>StandardOutPath</key>
|
|
52271
|
-
<string>${escapePlist(
|
|
52856
|
+
<string>${escapePlist(path4.join(config2.logDir, "stdout.log"))}</string>
|
|
52272
52857
|
<key>StandardErrorPath</key>
|
|
52273
|
-
<string>${escapePlist(
|
|
52858
|
+
<string>${escapePlist(path4.join(config2.logDir, "stderr.log"))}</string>
|
|
52274
52859
|
</dict>
|
|
52275
52860
|
</plist>
|
|
52276
52861
|
`;
|
|
@@ -52338,8 +52923,8 @@ async function installService(options) {
|
|
|
52338
52923
|
const paths = runnerServicePaths(options.homeDir, safeName);
|
|
52339
52924
|
const label = serviceLabel(safeName);
|
|
52340
52925
|
await mkdir2(paths.logDir, { recursive: true });
|
|
52341
|
-
await mkdir2(
|
|
52342
|
-
await mkdir2(
|
|
52926
|
+
await mkdir2(path4.dirname(paths.stateFile), { recursive: true });
|
|
52927
|
+
await mkdir2(path4.dirname(paths.launchAgentPath), { recursive: true });
|
|
52343
52928
|
if (options.userCode !== void 0) {
|
|
52344
52929
|
const paired = await pairRunnerOnce(options, safeName, paths.stateFile, options.userCode);
|
|
52345
52930
|
if (!paired) {
|
|
@@ -52494,8 +53079,8 @@ async function logsService(options) {
|
|
|
52494
53079
|
const safeName = safeRunnerName(options.runnerName);
|
|
52495
53080
|
const paths = runnerServicePaths(options.homeDir, safeName);
|
|
52496
53081
|
const label = serviceLabel(safeName);
|
|
52497
|
-
const stdoutLog =
|
|
52498
|
-
const stderrLog =
|
|
53082
|
+
const stdoutLog = path4.join(paths.logDir, "stdout.log");
|
|
53083
|
+
const stderrLog = path4.join(paths.logDir, "stderr.log");
|
|
52499
53084
|
options.io.stdout.write(
|
|
52500
53085
|
`${[
|
|
52501
53086
|
`runner service logs for ${label}:`,
|
|
@@ -52526,7 +53111,7 @@ async function uninstallService(options) {
|
|
|
52526
53111
|
await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
|
|
52527
53112
|
let removed = false;
|
|
52528
53113
|
try {
|
|
52529
|
-
await
|
|
53114
|
+
await rm3(paths.launchAgentPath, { force: true });
|
|
52530
53115
|
removed = true;
|
|
52531
53116
|
} catch (error51) {
|
|
52532
53117
|
options.io.stderr.write(
|
|
@@ -52599,10 +53184,10 @@ function serviceTargets(uid, label) {
|
|
|
52599
53184
|
}
|
|
52600
53185
|
async function inspectRunnerServiceInstallations(options = {}) {
|
|
52601
53186
|
const homeDir = options.homeDir ?? homedir2();
|
|
52602
|
-
const launchAgentsDir =
|
|
53187
|
+
const launchAgentsDir = path4.join(homeDir, "Library", "LaunchAgents");
|
|
52603
53188
|
let names = [];
|
|
52604
53189
|
try {
|
|
52605
|
-
const entries = await
|
|
53190
|
+
const entries = await readdir2(launchAgentsDir);
|
|
52606
53191
|
names = entries.map((entry) => runnerNameFromPlist(entry)).filter((value) => value !== null).sort((left, right) => left.localeCompare(right));
|
|
52607
53192
|
} catch {
|
|
52608
53193
|
return [];
|
|
@@ -52728,11 +53313,11 @@ function resolveNodePath() {
|
|
|
52728
53313
|
}
|
|
52729
53314
|
function buildServicePath(nodePath, homeDir) {
|
|
52730
53315
|
const entries = [
|
|
52731
|
-
|
|
53316
|
+
path4.dirname(nodePath),
|
|
52732
53317
|
"/opt/homebrew/bin",
|
|
52733
53318
|
"/usr/local/bin",
|
|
52734
|
-
|
|
52735
|
-
|
|
53319
|
+
path4.join(homeDir, ".local", "bin"),
|
|
53320
|
+
path4.join(homeDir, "bin"),
|
|
52736
53321
|
"/usr/bin",
|
|
52737
53322
|
"/bin",
|
|
52738
53323
|
"/usr/sbin",
|
|
@@ -52781,7 +53366,7 @@ function resolveBinPath() {
|
|
|
52781
53366
|
return cliBrand.binName;
|
|
52782
53367
|
}
|
|
52783
53368
|
try {
|
|
52784
|
-
return
|
|
53369
|
+
return realpathSync2(entry);
|
|
52785
53370
|
} catch {
|
|
52786
53371
|
return entry;
|
|
52787
53372
|
}
|
|
@@ -52920,6 +53505,10 @@ async function runDoctor(options) {
|
|
|
52920
53505
|
lines.push(`version: ${currentVersion ?? "unknown"}`);
|
|
52921
53506
|
const pathStatus = await checkPath(env);
|
|
52922
53507
|
lines.push(pathStatus);
|
|
53508
|
+
const forgeUnsupportedReason = forgeBuildUnsupportedReason(process.platform);
|
|
53509
|
+
if (forgeUnsupportedReason) {
|
|
53510
|
+
lines.push(`forge_build: unsupported - ${forgeUnsupportedReason}`);
|
|
53511
|
+
}
|
|
52923
53512
|
try {
|
|
52924
53513
|
const session = await store.read();
|
|
52925
53514
|
lines.push(`session: ${session ? `present (${store.describe()})` : `missing - run ${cliBrand.binName} login --device`}`);
|
|
@@ -53039,9 +53628,8 @@ import { promisify as promisify5 } from "node:util";
|
|
|
53039
53628
|
// src/forge-workspace.ts
|
|
53040
53629
|
import { execFile as execFileCallback4 } from "node:child_process";
|
|
53041
53630
|
import { createHash } from "node:crypto";
|
|
53042
|
-
import { chmod, mkdir as mkdir3, rm as
|
|
53043
|
-
import
|
|
53044
|
-
import path4 from "node:path";
|
|
53631
|
+
import { chmod, lstat, mkdir as mkdir3, mkdtemp, realpath, rm as rm4, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
53632
|
+
import path5 from "node:path";
|
|
53045
53633
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
53046
53634
|
import { promisify as promisify4 } from "node:util";
|
|
53047
53635
|
|
|
@@ -53178,20 +53766,20 @@ function shortRequestId(buildRequestId) {
|
|
|
53178
53766
|
return slice.length > 0 ? slice : createHash("sha1").update(buildRequestId).digest("hex").slice(0, 8);
|
|
53179
53767
|
}
|
|
53180
53768
|
function requestRoot(baseDir, buildRequestId) {
|
|
53181
|
-
return
|
|
53769
|
+
return path5.join(baseDir, "work", shortRequestId(buildRequestId));
|
|
53182
53770
|
}
|
|
53183
53771
|
function baseRepoDir(baseDir, buildRequestId) {
|
|
53184
|
-
return
|
|
53772
|
+
return path5.join(requestRoot(baseDir, buildRequestId), "base");
|
|
53185
53773
|
}
|
|
53186
53774
|
function branchName(buildRequestId, changeset) {
|
|
53187
53775
|
const short = shortRequestId(buildRequestId);
|
|
53188
53776
|
return changeset.total > 1 ? `forge/${short}-cs${changeset.ordinal}` : `forge/${short}`;
|
|
53189
53777
|
}
|
|
53190
53778
|
function buildWorktreeDir(baseDir, buildRequestId, ordinal) {
|
|
53191
|
-
return
|
|
53779
|
+
return path5.join(requestRoot(baseDir, buildRequestId), `cs${ordinal}`);
|
|
53192
53780
|
}
|
|
53193
53781
|
function reviewWorktreeDir(baseDir, buildRequestId, ordinal) {
|
|
53194
|
-
return
|
|
53782
|
+
return path5.join(requestRoot(baseDir, buildRequestId), `review-cs${ordinal}`);
|
|
53195
53783
|
}
|
|
53196
53784
|
function remoteUrlFor(options) {
|
|
53197
53785
|
if (options.remoteUrl) {
|
|
@@ -53201,14 +53789,14 @@ function remoteUrlFor(options) {
|
|
|
53201
53789
|
}
|
|
53202
53790
|
async function pathExists(target) {
|
|
53203
53791
|
try {
|
|
53204
|
-
await
|
|
53792
|
+
await stat3(target);
|
|
53205
53793
|
return true;
|
|
53206
53794
|
} catch {
|
|
53207
53795
|
return false;
|
|
53208
53796
|
}
|
|
53209
53797
|
}
|
|
53210
53798
|
async function isGitRepo(dir) {
|
|
53211
|
-
return await pathExists(
|
|
53799
|
+
return await pathExists(path5.join(dir, ".git")) || await pathExists(path5.join(dir, "HEAD"));
|
|
53212
53800
|
}
|
|
53213
53801
|
async function runGit(args, cwd, timeoutMs = LOCAL_GIT_TIMEOUT_MS) {
|
|
53214
53802
|
try {
|
|
@@ -53260,13 +53848,31 @@ async function withNetworkRetries(op, fn, retryCounter) {
|
|
|
53260
53848
|
}
|
|
53261
53849
|
throw new Error(`${op} exhausted retries: ${lastError instanceof Error ? redactSecrets(lastError.message) : redactSecrets(String(lastError))}`);
|
|
53262
53850
|
}
|
|
53263
|
-
async function
|
|
53264
|
-
|
|
53265
|
-
|
|
53266
|
-
|
|
53267
|
-
|
|
53268
|
-
|
|
53269
|
-
)
|
|
53851
|
+
async function canonicalAskpassBase(baseDir) {
|
|
53852
|
+
return path5.join(await realpath(path5.dirname(baseDir)), path5.basename(baseDir));
|
|
53853
|
+
}
|
|
53854
|
+
async function prepareAskpassTurnDir(baseDir) {
|
|
53855
|
+
await mkdir3(baseDir, { recursive: true, mode: 448 });
|
|
53856
|
+
const baseInfo = await lstat(baseDir);
|
|
53857
|
+
if (baseInfo.isSymbolicLink() || !baseInfo.isDirectory()) {
|
|
53858
|
+
throw new Error(`refusing to use askpass dir ${baseDir}: not a real directory (possible symlink attack)`);
|
|
53859
|
+
}
|
|
53860
|
+
if (typeof process.getuid === "function" && baseInfo.uid !== process.getuid()) {
|
|
53861
|
+
throw new Error(`refusing to use askpass dir ${baseDir}: not owned by this user (possible pre-planted dir)`);
|
|
53862
|
+
}
|
|
53863
|
+
await chmod(baseDir, 448);
|
|
53864
|
+
const turnDir = await mkdtemp(path5.join(baseDir, "rost-runner-askpass-"));
|
|
53865
|
+
const canonicalBase = await canonicalAskpassBase(baseDir);
|
|
53866
|
+
const realTurn = await realpath(turnDir);
|
|
53867
|
+
if (path5.dirname(realTurn) !== canonicalBase) {
|
|
53868
|
+
await rm4(turnDir, { recursive: true, force: true });
|
|
53869
|
+
throw new Error(`refusing to use askpass dir ${turnDir}: resolved outside ${canonicalBase} (possible symlink attack)`);
|
|
53870
|
+
}
|
|
53871
|
+
return turnDir;
|
|
53872
|
+
}
|
|
53873
|
+
async function openAskpassSession(credential, baseDir = RUNNER_ASKPASS_DIR) {
|
|
53874
|
+
const turnDir = await prepareAskpassTurnDir(baseDir);
|
|
53875
|
+
const helperPath = path5.join(turnDir, "askpass.sh");
|
|
53270
53876
|
const helper = [
|
|
53271
53877
|
"#!/bin/sh",
|
|
53272
53878
|
"# One-shot Forge GIT_ASKPASS helper. Contains no secret; reads the token from env.",
|
|
@@ -53289,7 +53895,7 @@ async function openAskpassSession(credential) {
|
|
|
53289
53895
|
return {
|
|
53290
53896
|
env,
|
|
53291
53897
|
cleanup: async () => {
|
|
53292
|
-
await
|
|
53898
|
+
await rm4(turnDir, { recursive: true, force: true });
|
|
53293
53899
|
}
|
|
53294
53900
|
};
|
|
53295
53901
|
}
|
|
@@ -53327,8 +53933,8 @@ async function localBranchExists(baseRepo, branch) {
|
|
|
53327
53933
|
async function worktreeRegistered(baseRepo, worktreeDir) {
|
|
53328
53934
|
try {
|
|
53329
53935
|
const listing = await runGit(["worktree", "list", "--porcelain"], baseRepo);
|
|
53330
|
-
const target =
|
|
53331
|
-
return listing.split("\n").some((line) => line.startsWith("worktree ") &&
|
|
53936
|
+
const target = path5.resolve(worktreeDir);
|
|
53937
|
+
return listing.split("\n").some((line) => line.startsWith("worktree ") && path5.resolve(line.slice("worktree ".length).trim()) === target);
|
|
53332
53938
|
} catch {
|
|
53333
53939
|
return false;
|
|
53334
53940
|
}
|
|
@@ -53339,7 +53945,7 @@ async function configureForgeIdentity(baseRepo) {
|
|
|
53339
53945
|
}
|
|
53340
53946
|
async function ensureBaseClone(input) {
|
|
53341
53947
|
const baseRepo = baseRepoDir(input.baseDir, input.buildRequestId);
|
|
53342
|
-
await mkdir3(
|
|
53948
|
+
await mkdir3(path5.dirname(baseRepo), { recursive: true, mode: 448 });
|
|
53343
53949
|
await withLedgerStep(input.ledger, "clone", (retryCounter) => withAskpass(input.credential, async (netEnv) => {
|
|
53344
53950
|
if (await isGitRepo(baseRepo)) {
|
|
53345
53951
|
await runGitNetwork("git fetch", ["fetch", "--prune", "--quiet", input.url, "+refs/heads/*:refs/remotes/origin/*"], netEnv, baseRepo, FETCH_TIMEOUT_MS, retryCounter);
|
|
@@ -53388,7 +53994,7 @@ async function prepareMergeWorkspace(options) {
|
|
|
53388
53994
|
ledger: options.ledger
|
|
53389
53995
|
});
|
|
53390
53996
|
const branch = options.headBranch;
|
|
53391
|
-
const worktreeDir =
|
|
53997
|
+
const worktreeDir = path5.join(requestRoot(options.baseDir, options.buildRequestId), "merge");
|
|
53392
53998
|
const originDefault = `origin/${options.repo.default_branch}`;
|
|
53393
53999
|
const originHead = `origin/${branch}`;
|
|
53394
54000
|
await withLedgerStep(options.ledger, "branch", async () => {
|
|
@@ -53425,7 +54031,7 @@ async function prepareReviewWorktree(options, baseRepo) {
|
|
|
53425
54031
|
if (await worktreeRegistered(baseRepo, worktreeDir)) {
|
|
53426
54032
|
await removeWorktree(baseRepo, worktreeDir);
|
|
53427
54033
|
} else if (await pathExists(worktreeDir)) {
|
|
53428
|
-
await
|
|
54034
|
+
await rm4(worktreeDir, { recursive: true, force: true });
|
|
53429
54035
|
await runGit(["worktree", "prune"], baseRepo).catch(() => void 0);
|
|
53430
54036
|
}
|
|
53431
54037
|
await runGit(["worktree", "add", "--detach", worktreeDir, headSha], baseRepo);
|
|
@@ -53758,267 +54364,18 @@ async function removeWorktree(baseRepo, worktreeDir) {
|
|
|
53758
54364
|
try {
|
|
53759
54365
|
await runGit(["worktree", "remove", "--force", worktreeDir], baseRepo);
|
|
53760
54366
|
} catch {
|
|
53761
|
-
await
|
|
54367
|
+
await rm4(worktreeDir, { recursive: true, force: true });
|
|
53762
54368
|
}
|
|
53763
54369
|
await runGit(["worktree", "prune"], baseRepo).catch(() => void 0);
|
|
53764
54370
|
}
|
|
53765
54371
|
async function removeWorkspace(ws) {
|
|
53766
54372
|
if (!await pathExists(ws.baseRepo)) {
|
|
53767
|
-
await
|
|
54373
|
+
await rm4(ws.worktreeDir, { recursive: true, force: true });
|
|
53768
54374
|
return;
|
|
53769
54375
|
}
|
|
53770
54376
|
await removeWorktree(ws.baseRepo, ws.worktreeDir);
|
|
53771
54377
|
}
|
|
53772
54378
|
|
|
53773
|
-
// src/runner-sandbox.ts
|
|
53774
|
-
import { existsSync as existsSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
53775
|
-
import { readdir as readdir2, rm as rm4, stat as stat3 } from "node:fs/promises";
|
|
53776
|
-
import path5 from "node:path";
|
|
53777
|
-
var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
|
|
53778
|
-
var RUNNER_TEMP_PREFIXES = [
|
|
53779
|
-
"rost-runner-mcp-",
|
|
53780
|
-
"rost-runner-sandbox-",
|
|
53781
|
-
"rost-runner-askpass-"
|
|
53782
|
-
];
|
|
53783
|
-
var SENSITIVE_READ_SUBPATHS = [
|
|
53784
|
-
".ssh",
|
|
53785
|
-
".aws",
|
|
53786
|
-
".gnupg",
|
|
53787
|
-
".config/gcloud",
|
|
53788
|
-
".config/gh",
|
|
53789
|
-
".kube",
|
|
53790
|
-
".docker",
|
|
53791
|
-
".npmrc"
|
|
53792
|
-
];
|
|
53793
|
-
var SENSITIVE_READ_LITERALS = [".netrc"];
|
|
53794
|
-
var SENSITIVE_WRITE_SUBPATHS = [
|
|
53795
|
-
".ssh",
|
|
53796
|
-
".aws",
|
|
53797
|
-
".gnupg",
|
|
53798
|
-
".config/git",
|
|
53799
|
-
"bin",
|
|
53800
|
-
"Library/LaunchAgents"
|
|
53801
|
-
];
|
|
53802
|
-
var SENSITIVE_WRITE_LITERALS = [
|
|
53803
|
-
".gitconfig",
|
|
53804
|
-
".zshrc",
|
|
53805
|
-
".zprofile",
|
|
53806
|
-
".zshenv",
|
|
53807
|
-
".zlogin",
|
|
53808
|
-
".bashrc",
|
|
53809
|
-
".bash_profile",
|
|
53810
|
-
".bash_login",
|
|
53811
|
-
".profile"
|
|
53812
|
-
];
|
|
53813
|
-
function parseSandboxMode(value) {
|
|
53814
|
-
const normalized = (value ?? "auto").trim().toLowerCase();
|
|
53815
|
-
if (normalized === "off" || normalized === "strict" || normalized === "auto" || normalized === "") {
|
|
53816
|
-
return normalized === "" ? "auto" : normalized;
|
|
53817
|
-
}
|
|
53818
|
-
return "auto";
|
|
53819
|
-
}
|
|
53820
|
-
function parseSandboxNetwork(value) {
|
|
53821
|
-
const normalized = (value ?? "").trim().toLowerCase();
|
|
53822
|
-
if (normalized === "" || normalized === "allow") {
|
|
53823
|
-
return { allowNetwork: true, unrecognized: null };
|
|
53824
|
-
}
|
|
53825
|
-
if (normalized === "deny") {
|
|
53826
|
-
return { allowNetwork: false, unrecognized: null };
|
|
53827
|
-
}
|
|
53828
|
-
return { allowNetwork: true, unrecognized: value ?? "" };
|
|
53829
|
-
}
|
|
53830
|
-
function sbplString(value) {
|
|
53831
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
53832
|
-
}
|
|
53833
|
-
function subpath(value) {
|
|
53834
|
-
return `(subpath ${sbplString(value)})`;
|
|
53835
|
-
}
|
|
53836
|
-
function literal2(value) {
|
|
53837
|
-
return `(literal ${sbplString(value)})`;
|
|
53838
|
-
}
|
|
53839
|
-
var CLAUDE_TEMP_SBPL_REGEX = '(regex #"^/private/tmp/claude-[^/]+")';
|
|
53840
|
-
var STRICT_SYSTEM_READ_SUBPATHS = [
|
|
53841
|
-
"/usr",
|
|
53842
|
-
"/bin",
|
|
53843
|
-
"/sbin",
|
|
53844
|
-
"/System",
|
|
53845
|
-
"/Library",
|
|
53846
|
-
"/private/etc",
|
|
53847
|
-
"/private/var/db",
|
|
53848
|
-
"/dev",
|
|
53849
|
-
"/opt",
|
|
53850
|
-
"/nix"
|
|
53851
|
-
];
|
|
53852
|
-
function buildSeatbeltProfile(input) {
|
|
53853
|
-
const home = input.homeDir.replace(/\/+$/, "");
|
|
53854
|
-
const lines = ["(version 1)"];
|
|
53855
|
-
const denyReadBaseSubpaths = input.denyReadPaths.filter((p) => p.endsWith("/")).map((p) => subpath(p.replace(/\/+$/, "")));
|
|
53856
|
-
const denyReadLiterals = input.denyReadPaths.filter((p) => !p.endsWith("/")).map(literal2);
|
|
53857
|
-
const denyWriteSubpaths = input.denyWritePaths.filter((p) => p.endsWith("/")).map((p) => subpath(p.replace(/\/+$/, "")));
|
|
53858
|
-
const denyWriteLiterals = input.denyWritePaths.filter((p) => !p.endsWith("/")).map(literal2);
|
|
53859
|
-
const credentialReadDenies = [
|
|
53860
|
-
...SENSITIVE_READ_SUBPATHS.map((rel) => subpath(`${home}/${rel}`)),
|
|
53861
|
-
...SENSITIVE_READ_LITERALS.map((rel) => literal2(`${home}/${rel}`))
|
|
53862
|
-
];
|
|
53863
|
-
if (input.strict) {
|
|
53864
|
-
lines.push("(deny default)");
|
|
53865
|
-
lines.push("(allow process*)");
|
|
53866
|
-
lines.push("(allow sysctl-read)");
|
|
53867
|
-
lines.push("(allow mach-lookup)");
|
|
53868
|
-
lines.push("(allow iokit-open)");
|
|
53869
|
-
lines.push("(allow system-socket)");
|
|
53870
|
-
lines.push("(allow signal (target self))");
|
|
53871
|
-
lines.push("(allow file-read-metadata)");
|
|
53872
|
-
const strictReads = [
|
|
53873
|
-
...STRICT_SYSTEM_READ_SUBPATHS.map(subpath),
|
|
53874
|
-
subpath(`${home}/.claude`),
|
|
53875
|
-
subpath(`${home}/.codex`),
|
|
53876
|
-
subpath(input.tmpDir)
|
|
53877
|
-
];
|
|
53878
|
-
lines.push(`(allow file-read* ${strictReads.join(" ")})`);
|
|
53879
|
-
} else {
|
|
53880
|
-
lines.push("(allow default)");
|
|
53881
|
-
}
|
|
53882
|
-
lines.push("(deny appleevent-send)");
|
|
53883
|
-
if (denyReadBaseSubpaths.length > 0) {
|
|
53884
|
-
lines.push(`(deny file-read* ${denyReadBaseSubpaths.join(" ")})`);
|
|
53885
|
-
}
|
|
53886
|
-
const workspaceReads = [subpath(input.workspaceDir), ...input.allowWritePaths.map(subpath), CLAUDE_TEMP_SBPL_REGEX];
|
|
53887
|
-
lines.push(`(allow file-read* ${workspaceReads.join(" ")})`);
|
|
53888
|
-
lines.push("(deny file-write*)");
|
|
53889
|
-
const allowWrites = [
|
|
53890
|
-
subpath(input.workspaceDir),
|
|
53891
|
-
subpath(input.tmpDir),
|
|
53892
|
-
subpath(`${home}/.claude`),
|
|
53893
|
-
subpath(`${home}/.codex`),
|
|
53894
|
-
...input.allowWritePaths.map(subpath),
|
|
53895
|
-
CLAUDE_TEMP_SBPL_REGEX,
|
|
53896
|
-
// DER-1398: Claude Code's per-session + per-command temp dirs.
|
|
53897
|
-
subpath("/dev/fd"),
|
|
53898
|
-
literal2("/dev/null"),
|
|
53899
|
-
literal2("/dev/stdout"),
|
|
53900
|
-
literal2("/dev/stderr"),
|
|
53901
|
-
literal2("/dev/tty"),
|
|
53902
|
-
literal2("/dev/dtracehelper")
|
|
53903
|
-
];
|
|
53904
|
-
lines.push(`(allow file-write* ${allowWrites.join(" ")})`);
|
|
53905
|
-
lines.push(`(deny file-read* ${[...credentialReadDenies, ...denyReadLiterals].join(" ")})`);
|
|
53906
|
-
const denyWrites = [
|
|
53907
|
-
...SENSITIVE_WRITE_SUBPATHS.map((rel) => subpath(`${home}/${rel}`)),
|
|
53908
|
-
...SENSITIVE_WRITE_LITERALS.map((rel) => literal2(`${home}/${rel}`)),
|
|
53909
|
-
// Caller-supplied write-denies (e.g. `<baseRepo>/.git/hooks` + `.git/config`) last so
|
|
53910
|
-
// they override the allowWritePaths re-allow of the shared object store above, while
|
|
53911
|
-
// `.git/{objects,refs,worktrees,logs,index,HEAD}` stay writable for local commits.
|
|
53912
|
-
...denyWriteSubpaths,
|
|
53913
|
-
...denyWriteLiterals
|
|
53914
|
-
];
|
|
53915
|
-
lines.push(`(deny file-write* ${denyWrites.join(" ")})`);
|
|
53916
|
-
lines.push('(deny mach-lookup (global-name "com.apple.coreservices.appleevents"))');
|
|
53917
|
-
if (!input.allowNetwork) {
|
|
53918
|
-
lines.push("(deny network*)");
|
|
53919
|
-
} else if (input.strict) {
|
|
53920
|
-
lines.push("(allow network*)");
|
|
53921
|
-
}
|
|
53922
|
-
return `${lines.join("\n")}
|
|
53923
|
-
`;
|
|
53924
|
-
}
|
|
53925
|
-
function canonical(p) {
|
|
53926
|
-
try {
|
|
53927
|
-
return realpathSync2(p);
|
|
53928
|
-
} catch {
|
|
53929
|
-
return p;
|
|
53930
|
-
}
|
|
53931
|
-
}
|
|
53932
|
-
function canonicalFile(p) {
|
|
53933
|
-
return path5.join(canonical(path5.dirname(p)), path5.basename(p));
|
|
53934
|
-
}
|
|
53935
|
-
function isSandboxExecAvailable() {
|
|
53936
|
-
try {
|
|
53937
|
-
return existsSync2(SANDBOX_EXEC_PATH);
|
|
53938
|
-
} catch {
|
|
53939
|
-
return false;
|
|
53940
|
-
}
|
|
53941
|
-
}
|
|
53942
|
-
function resolveSandboxSpec(options) {
|
|
53943
|
-
if (options.mode === "off") {
|
|
53944
|
-
return { kind: "none", reason: "RUNNER_SANDBOX=off (loosened fallback - OS confinement disabled)" };
|
|
53945
|
-
}
|
|
53946
|
-
if (options.platform !== "darwin") {
|
|
53947
|
-
return { kind: "none", reason: `no OS sandbox on ${options.platform} (seatbelt is macOS-only; Linux confinement is a follow-up)` };
|
|
53948
|
-
}
|
|
53949
|
-
const available = options.sandboxExecAvailable ?? isSandboxExecAvailable();
|
|
53950
|
-
if (!available) {
|
|
53951
|
-
return { kind: "none", reason: `sandbox-exec not found at ${SANDBOX_EXEC_PATH}` };
|
|
53952
|
-
}
|
|
53953
|
-
const workspaceDir = canonical(options.workspaceDir);
|
|
53954
|
-
const tmpDir = canonical(options.tmpDir);
|
|
53955
|
-
const homeDir = canonical(options.homeDir);
|
|
53956
|
-
const allowNetwork = options.allowNetwork ?? true;
|
|
53957
|
-
const denyReadPaths = [canonicalFile(options.stateFile)];
|
|
53958
|
-
if (options.runnerBaseDir) {
|
|
53959
|
-
denyReadPaths.push(`${canonical(options.runnerBaseDir).replace(/\/+$/, "")}/`);
|
|
53960
|
-
}
|
|
53961
|
-
const denyWritePaths = (options.denyWritePaths ?? []).map(
|
|
53962
|
-
(p) => p.endsWith("/") ? `${canonical(p.replace(/\/+$/, ""))}/` : canonicalFile(p)
|
|
53963
|
-
);
|
|
53964
|
-
const allowWritePaths = (options.allowWritePaths ?? []).map(canonical);
|
|
53965
|
-
const profile = buildSeatbeltProfile({
|
|
53966
|
-
workspaceDir,
|
|
53967
|
-
tmpDir,
|
|
53968
|
-
homeDir,
|
|
53969
|
-
denyReadPaths,
|
|
53970
|
-
allowWritePaths,
|
|
53971
|
-
denyWritePaths,
|
|
53972
|
-
allowNetwork,
|
|
53973
|
-
strict: options.mode === "strict"
|
|
53974
|
-
});
|
|
53975
|
-
return {
|
|
53976
|
-
kind: "seatbelt",
|
|
53977
|
-
reason: options.mode === "strict" ? "seatbelt (strict: deny-default reads + write confinement)" : "seatbelt (guard: deny sensitive reads/writes)",
|
|
53978
|
-
profile,
|
|
53979
|
-
allowNetwork
|
|
53980
|
-
};
|
|
53981
|
-
}
|
|
53982
|
-
function wrapCommandWithSandbox(spec, command, args, profilePath) {
|
|
53983
|
-
if (spec.kind === "none" || profilePath === null) {
|
|
53984
|
-
return { command, args, profilePath: null };
|
|
53985
|
-
}
|
|
53986
|
-
return {
|
|
53987
|
-
command: SANDBOX_EXEC_PATH,
|
|
53988
|
-
args: ["-f", profilePath, command, ...args],
|
|
53989
|
-
profilePath
|
|
53990
|
-
};
|
|
53991
|
-
}
|
|
53992
|
-
async function sweepStaleRunnerTempFiles(dir, options = {}) {
|
|
53993
|
-
const maxAgeMs = options.maxAgeMs ?? 60 * 60 * 1e3;
|
|
53994
|
-
const now = options.now ?? Date.now();
|
|
53995
|
-
let entries;
|
|
53996
|
-
try {
|
|
53997
|
-
entries = await readdir2(dir);
|
|
53998
|
-
} catch {
|
|
53999
|
-
return [];
|
|
54000
|
-
}
|
|
54001
|
-
const removed = [];
|
|
54002
|
-
await Promise.all(
|
|
54003
|
-
entries.map(async (name) => {
|
|
54004
|
-
if (!RUNNER_TEMP_PREFIXES.some((prefix) => name.startsWith(prefix))) {
|
|
54005
|
-
return;
|
|
54006
|
-
}
|
|
54007
|
-
const full = path5.join(dir, name);
|
|
54008
|
-
try {
|
|
54009
|
-
const info = await stat3(full);
|
|
54010
|
-
if (now - info.mtimeMs < maxAgeMs) {
|
|
54011
|
-
return;
|
|
54012
|
-
}
|
|
54013
|
-
await rm4(full, { force: true });
|
|
54014
|
-
removed.push(name);
|
|
54015
|
-
} catch {
|
|
54016
|
-
}
|
|
54017
|
-
})
|
|
54018
|
-
);
|
|
54019
|
-
return removed;
|
|
54020
|
-
}
|
|
54021
|
-
|
|
54022
54379
|
// src/runner-serve.ts
|
|
54023
54380
|
var execFile5 = promisify5(execFileCallback5);
|
|
54024
54381
|
var RUNNER_WORK_ORDER_TOOLS = [
|
|
@@ -54034,7 +54391,6 @@ var RUNNER_AICOS_TURN_TOOLS = [
|
|
|
54034
54391
|
var CLAUDE_WORKSPACE_WRITE_LOCAL_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
54035
54392
|
var CLAUDE_READ_LOCAL_TOOLS = ["Read", "Glob", "Grep"];
|
|
54036
54393
|
var READ_STUB_TIMEOUT_MS = 18e4;
|
|
54037
|
-
var FORGE_TURN_HARD_CEILING_SECONDS = 30 * 60;
|
|
54038
54394
|
function claudeMcpToolName(name) {
|
|
54039
54395
|
return name.startsWith("mcp__") ? name : `mcp__rost__${name}`;
|
|
54040
54396
|
}
|
|
@@ -54062,12 +54418,19 @@ ${usage()}
|
|
|
54062
54418
|
`);
|
|
54063
54419
|
if (config2.sandboxNetworkWarning) {
|
|
54064
54420
|
options.io.stderr.write(`warning: ${config2.sandboxNetworkWarning}
|
|
54421
|
+
`);
|
|
54422
|
+
}
|
|
54423
|
+
const forgeUnsupportedReason = forgeBuildUnsupportedReason(process.platform);
|
|
54424
|
+
if (forgeUnsupportedReason) {
|
|
54425
|
+
options.io.stdout.write(`forge build: ${forgeUnsupportedReason}
|
|
54065
54426
|
`);
|
|
54066
54427
|
}
|
|
54067
54428
|
try {
|
|
54068
54429
|
const swept = await sweepStaleRunnerTempFiles(tmpdir2());
|
|
54069
|
-
|
|
54070
|
-
|
|
54430
|
+
const sweptAskpass = await sweepStaleRunnerTempFiles(RUNNER_ASKPASS_DIR);
|
|
54431
|
+
const total = swept.length + sweptAskpass.length;
|
|
54432
|
+
if (total > 0) {
|
|
54433
|
+
options.io.stdout.write(`swept ${total} stale runner temp file(s)
|
|
54071
54434
|
`);
|
|
54072
54435
|
}
|
|
54073
54436
|
} catch {
|
|
@@ -54104,9 +54467,19 @@ ${usage()}
|
|
|
54104
54467
|
}
|
|
54105
54468
|
if (localRuntime) {
|
|
54106
54469
|
activeSessions += 1;
|
|
54470
|
+
const stopTurnHeartbeat = startTurnHeartbeat({
|
|
54471
|
+
fetchImpl,
|
|
54472
|
+
appUrl: options.appUrl,
|
|
54473
|
+
secret: state.runner_secret,
|
|
54474
|
+
intervalMs: config2.heartbeatMs,
|
|
54475
|
+
capabilities,
|
|
54476
|
+
io: options.io,
|
|
54477
|
+
telemetry: () => detectTelemetry(capabilities, env, activeSessions, cliVersion)
|
|
54478
|
+
});
|
|
54107
54479
|
try {
|
|
54108
54480
|
await claimAndExecute(fetchImpl, options.appUrl, state, config2, options.io, localRuntime);
|
|
54109
54481
|
} finally {
|
|
54482
|
+
stopTurnHeartbeat();
|
|
54110
54483
|
activeSessions = Math.max(0, activeSessions - 1);
|
|
54111
54484
|
}
|
|
54112
54485
|
}
|
|
@@ -54138,6 +54511,44 @@ async function waitForNextHeartbeat(fetchImpl, appUrl2, state, config2, io, runt
|
|
|
54138
54511
|
}
|
|
54139
54512
|
}
|
|
54140
54513
|
}
|
|
54514
|
+
function startTurnHeartbeat(deps) {
|
|
54515
|
+
let stopped = false;
|
|
54516
|
+
let inFlight = false;
|
|
54517
|
+
const timer = setInterval(() => {
|
|
54518
|
+
if (stopped || inFlight) {
|
|
54519
|
+
return;
|
|
54520
|
+
}
|
|
54521
|
+
inFlight = true;
|
|
54522
|
+
const controller = new AbortController();
|
|
54523
|
+
const timeout = setTimeout(() => controller.abort(), deps.intervalMs);
|
|
54524
|
+
timeout.unref?.();
|
|
54525
|
+
void (async () => {
|
|
54526
|
+
try {
|
|
54527
|
+
const response = await post2(deps.fetchImpl, deps.appUrl, "/api/runner/heartbeat", {
|
|
54528
|
+
capabilities: deps.capabilities,
|
|
54529
|
+
telemetry: deps.telemetry()
|
|
54530
|
+
}, deps.secret, controller.signal);
|
|
54531
|
+
if (!stopped && response.status !== 200) {
|
|
54532
|
+
deps.io.stderr.write(`turn heartbeat ${response.status}: ${redactForLog(JSON.stringify(response.json))}
|
|
54533
|
+
`);
|
|
54534
|
+
}
|
|
54535
|
+
} catch (error51) {
|
|
54536
|
+
if (!stopped) {
|
|
54537
|
+
deps.io.stderr.write(`turn heartbeat error (continuing): ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
|
|
54538
|
+
`);
|
|
54539
|
+
}
|
|
54540
|
+
} finally {
|
|
54541
|
+
clearTimeout(timeout);
|
|
54542
|
+
inFlight = false;
|
|
54543
|
+
}
|
|
54544
|
+
})();
|
|
54545
|
+
}, deps.intervalMs);
|
|
54546
|
+
timer.unref?.();
|
|
54547
|
+
return () => {
|
|
54548
|
+
stopped = true;
|
|
54549
|
+
clearInterval(timer);
|
|
54550
|
+
};
|
|
54551
|
+
}
|
|
54141
54552
|
function resolveAccountAlias(env) {
|
|
54142
54553
|
return (env.RUNNER_MODEL_ACCOUNT_ALIAS ?? env.ROST_RUNNER_MODEL_ACCOUNT_ALIAS ?? "").trim() || "default";
|
|
54143
54554
|
}
|
|
@@ -54518,7 +54929,7 @@ async function probeForgeDiskFreeMb(homeDir) {
|
|
|
54518
54929
|
return null;
|
|
54519
54930
|
}
|
|
54520
54931
|
}
|
|
54521
|
-
async function post2(fetchImpl, appUrl2, pathName, body, token) {
|
|
54932
|
+
async function post2(fetchImpl, appUrl2, pathName, body, token, signal) {
|
|
54522
54933
|
const headers = { "content-type": "application/json" };
|
|
54523
54934
|
if (token) {
|
|
54524
54935
|
headers.authorization = `Bearer ${token}`;
|
|
@@ -54526,7 +54937,10 @@ async function post2(fetchImpl, appUrl2, pathName, body, token) {
|
|
|
54526
54937
|
const response = await fetchImpl(`${appUrl2.replace(/\/+$/, "")}${pathName}`, {
|
|
54527
54938
|
method: "POST",
|
|
54528
54939
|
headers,
|
|
54529
|
-
body: JSON.stringify(body)
|
|
54940
|
+
body: JSON.stringify(body),
|
|
54941
|
+
// Optional per-call abort. Callers that pass no signal are unchanged; only the turn
|
|
54942
|
+
// keepalive uses it to bound a hung POST so its in-flight guard always releases (DER-1484).
|
|
54943
|
+
...signal ? { signal } : {}
|
|
54530
54944
|
});
|
|
54531
54945
|
const text = await response.text();
|
|
54532
54946
|
let json2;
|
|
@@ -55003,6 +55417,13 @@ function buildTurnPrompt(input) {
|
|
|
55003
55417
|
`Claimed ${kind === "turn_execution" ? "turn execution" : "work order"} context: ${JSON.stringify(context).slice(0, 8e3)}`
|
|
55004
55418
|
].filter((line) => line.length > 0).join("\n");
|
|
55005
55419
|
}
|
|
55420
|
+
async function sweepOrphanedTurnTempFiles() {
|
|
55421
|
+
try {
|
|
55422
|
+
await sweepStaleRunnerTempFiles(tmpdir2(), { maxAgeMs: RUNNER_TEMP_TURN_SWEEP_MAX_AGE_MS });
|
|
55423
|
+
await sweepStaleRunnerTempFiles(RUNNER_ASKPASS_DIR, { maxAgeMs: RUNNER_TEMP_TURN_SWEEP_MAX_AGE_MS });
|
|
55424
|
+
} catch {
|
|
55425
|
+
}
|
|
55426
|
+
}
|
|
55006
55427
|
async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
|
|
55007
55428
|
const execution = parseRunnerExecutionContract(workOrder);
|
|
55008
55429
|
if (execution?.profile === "forge_merge") {
|
|
@@ -55085,6 +55506,7 @@ async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
|
|
|
55085
55506
|
if (sandboxProfilePath) {
|
|
55086
55507
|
void rm5(sandboxProfilePath, { force: true });
|
|
55087
55508
|
}
|
|
55509
|
+
void sweepOrphanedTurnTempFiles();
|
|
55088
55510
|
if (forgePrep) {
|
|
55089
55511
|
if (plan.profile === "forge_build" && result.ok && forgePrep.credential.canPush) {
|
|
55090
55512
|
result = await finalizeForgeBuildPush(ctx, forgePrep, result);
|
|
@@ -55276,6 +55698,7 @@ async function spawnMergeConflictModel(ctx, input) {
|
|
|
55276
55698
|
if (sandboxProfilePath) {
|
|
55277
55699
|
void rm5(sandboxProfilePath, { force: true });
|
|
55278
55700
|
}
|
|
55701
|
+
void sweepOrphanedTurnTempFiles();
|
|
55279
55702
|
return result;
|
|
55280
55703
|
}
|
|
55281
55704
|
async function runForgeMergeTurn(ctx, workOrder, execution, runtime) {
|
|
@@ -55373,6 +55796,20 @@ async function executeForgeMerge(ctx, input) {
|
|
|
55373
55796
|
function scrubTranscriptSessionIds(value) {
|
|
55374
55797
|
return value.replace(/("?(?:session_id|sessionId)"?\s*[:=]\s*"?)[A-Za-z0-9._-]+("?)/gi, "$1[redacted]$2");
|
|
55375
55798
|
}
|
|
55799
|
+
function killProcessTree(child) {
|
|
55800
|
+
const pid = child.pid;
|
|
55801
|
+
if (typeof pid === "number") {
|
|
55802
|
+
try {
|
|
55803
|
+
process.kill(-pid, "SIGKILL");
|
|
55804
|
+
return;
|
|
55805
|
+
} catch {
|
|
55806
|
+
}
|
|
55807
|
+
}
|
|
55808
|
+
try {
|
|
55809
|
+
child.kill("SIGKILL");
|
|
55810
|
+
} catch {
|
|
55811
|
+
}
|
|
55812
|
+
}
|
|
55376
55813
|
function runModelProcess(input) {
|
|
55377
55814
|
return new Promise((resolve2) => {
|
|
55378
55815
|
let settled = false;
|
|
@@ -55386,12 +55823,20 @@ function runModelProcess(input) {
|
|
|
55386
55823
|
const child = spawn(input.command, input.args, {
|
|
55387
55824
|
cwd: input.cwd,
|
|
55388
55825
|
env: input.env,
|
|
55389
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
55826
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
55827
|
+
// DER-1569 FIX 1: give the turn its OWN process group so a timeout SIGKILL reaps the
|
|
55828
|
+
// WHOLE tree, not just the direct child. sandbox-exec execvp's into the model (pid
|
|
55829
|
+
// preserved), and the model's Bash tool forks build/test grandchildren that inherit this
|
|
55830
|
+
// pgid; a plain `child.kill()` signals only the direct pid and orphans them to init.
|
|
55831
|
+
// `detached` makes the child a group leader (pgid == child.pid) so `process.kill(-pid)`
|
|
55832
|
+
// in killProcessTree hits the model and every descendant. We still await `close`, so the
|
|
55833
|
+
// reference is kept (no `unref()`); the parent lifecycle is unchanged.
|
|
55834
|
+
detached: true
|
|
55390
55835
|
});
|
|
55391
55836
|
let out = "";
|
|
55392
55837
|
let err = "";
|
|
55393
55838
|
const timer = setTimeout(() => {
|
|
55394
|
-
child
|
|
55839
|
+
killProcessTree(child);
|
|
55395
55840
|
const partial2 = scrubTranscriptSessionIds(redactForLog((out || err || "").trim()));
|
|
55396
55841
|
finish({
|
|
55397
55842
|
ok: false,
|
|
@@ -55609,6 +56054,15 @@ function usage() {
|
|
|
55609
56054
|
}
|
|
55610
56055
|
|
|
55611
56056
|
// src/index.ts
|
|
56057
|
+
async function defaultConfirmPrompt(question) {
|
|
56058
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
56059
|
+
try {
|
|
56060
|
+
const answer = (await rl.question(`${question} `)).trim().toLowerCase();
|
|
56061
|
+
return answer === "y" || answer === "yes";
|
|
56062
|
+
} finally {
|
|
56063
|
+
rl.close();
|
|
56064
|
+
}
|
|
56065
|
+
}
|
|
55612
56066
|
async function main(argv = process.argv.slice(2), options = {}) {
|
|
55613
56067
|
const io = options.io ?? {
|
|
55614
56068
|
stdout: process.stdout,
|
|
@@ -55617,7 +56071,10 @@ async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
55617
56071
|
// and stdout must be TTYs. A piped/headless/agent session fails this and so
|
|
55618
56072
|
// will not auto-approve its own mint/revoke confirmation unless skip-
|
|
55619
56073
|
// permissions is explicitly enabled.
|
|
55620
|
-
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
56074
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
56075
|
+
// DER-1490: a real session prompts the human on stdin before approving a
|
|
56076
|
+
// pending confirmation gate.
|
|
56077
|
+
confirm: defaultConfirmPrompt
|
|
55621
56078
|
};
|
|
55622
56079
|
const login = options.login ?? loginWithBrowser;
|
|
55623
56080
|
const deviceLogin = options.deviceLogin ?? loginWithDeviceCode;
|
|
@@ -56047,11 +56504,13 @@ async function executeMcp(io, client, appUrl2, args, env = process.env) {
|
|
|
56047
56504
|
const options = parseMcpInstallOptions(args.slice(1));
|
|
56048
56505
|
const envSkipPermissions = env[`${cliBrand.envPrefix}_CLI_SKIP_PERMISSIONS`] === "1";
|
|
56049
56506
|
const canAutoApprove = io.interactive === true || options.skipPermissions || envSkipPermissions;
|
|
56507
|
+
const reviewed = io.interactive === true;
|
|
56050
56508
|
const rendered = await renderMcpInstall({
|
|
56051
56509
|
client,
|
|
56052
56510
|
appUrl: appUrl2,
|
|
56053
56511
|
options,
|
|
56054
|
-
canAutoApprove
|
|
56512
|
+
canAutoApprove,
|
|
56513
|
+
reviewed
|
|
56055
56514
|
});
|
|
56056
56515
|
io.stdout.write(rendered.stdout);
|
|
56057
56516
|
if (rendered.stderr) {
|
|
@@ -56121,15 +56580,21 @@ async function printCommandOutput(io, client, commandId, body = {}, format, opti
|
|
|
56121
56580
|
return 0;
|
|
56122
56581
|
} catch (error51) {
|
|
56123
56582
|
const envAutoConfirm = process.env[`${cliBrand.envPrefix}_CLI_AUTO_CONFIRM`] === "1";
|
|
56124
|
-
|
|
56125
|
-
|
|
56126
|
-
|
|
56127
|
-
|
|
56128
|
-
io.
|
|
56129
|
-
|
|
56583
|
+
const pending = pendingConfirmationFromError2(error51);
|
|
56584
|
+
if (pending && pending.commandId === commandId && (io.interactive === true || options.autoConfirm === true || envAutoConfirm)) {
|
|
56585
|
+
renderPendingConfirmationGate3(io, pending);
|
|
56586
|
+
if (io.interactive === true) {
|
|
56587
|
+
const approved = io.confirm !== void 0 && await io.confirm(`Approve ${pending.commandId} (confirmation ${pending.confirmationId})? [y/N]`);
|
|
56588
|
+
if (!approved) {
|
|
56589
|
+
io.stderr.write("Not approved.\n");
|
|
56590
|
+
return 1;
|
|
56591
|
+
}
|
|
56130
56592
|
try {
|
|
56131
|
-
const
|
|
56132
|
-
|
|
56593
|
+
const approvedResult = await client.execute("confirmation.approve", {
|
|
56594
|
+
confirmation_id: pending.confirmationId,
|
|
56595
|
+
reviewed: true
|
|
56596
|
+
});
|
|
56597
|
+
const approvedOutput = asRecord3(approvedResult.output).output ?? approvedResult.output;
|
|
56133
56598
|
const rendered = format ? format(approvedOutput) : JSON.stringify(approvedOutput, null, 2);
|
|
56134
56599
|
io.stdout.write(`${rendered}
|
|
56135
56600
|
`);
|
|
@@ -56138,6 +56603,19 @@ async function printCommandOutput(io, client, commandId, body = {}, format, opti
|
|
|
56138
56603
|
return printCommandError2(io, approvalError);
|
|
56139
56604
|
}
|
|
56140
56605
|
}
|
|
56606
|
+
const reason = options.autoConfirm === true ? "explicit CLI auto-confirm" : `${cliBrand.envPrefix}_CLI_AUTO_CONFIRM environment auto-confirm`;
|
|
56607
|
+
io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the ${reason}.
|
|
56608
|
+
`);
|
|
56609
|
+
try {
|
|
56610
|
+
const approvedResult = await client.execute("confirmation.approve", { confirmation_id: pending.confirmationId });
|
|
56611
|
+
const approvedOutput = asRecord3(approvedResult.output).output ?? approvedResult.output;
|
|
56612
|
+
const rendered = format ? format(approvedOutput) : JSON.stringify(approvedOutput, null, 2);
|
|
56613
|
+
io.stdout.write(`${rendered}
|
|
56614
|
+
`);
|
|
56615
|
+
return 0;
|
|
56616
|
+
} catch (approvalError) {
|
|
56617
|
+
return printCommandError2(io, approvalError);
|
|
56618
|
+
}
|
|
56141
56619
|
}
|
|
56142
56620
|
return printCommandError2(io, error51);
|
|
56143
56621
|
}
|
|
@@ -56160,7 +56638,32 @@ function pendingConfirmationFromError2(error51) {
|
|
|
56160
56638
|
if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
|
|
56161
56639
|
return null;
|
|
56162
56640
|
}
|
|
56163
|
-
|
|
56641
|
+
const summary = typeof details.summary === "string" ? details.summary : void 0;
|
|
56642
|
+
const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
|
|
56643
|
+
return {
|
|
56644
|
+
confirmationId,
|
|
56645
|
+
commandId,
|
|
56646
|
+
...summary === void 0 ? {} : { summary },
|
|
56647
|
+
...riskLevel === void 0 ? {} : { riskLevel },
|
|
56648
|
+
...details.diff === void 0 ? {} : { diff: details.diff }
|
|
56649
|
+
};
|
|
56650
|
+
}
|
|
56651
|
+
function renderPendingConfirmationGate3(io, pending) {
|
|
56652
|
+
io.stderr.write(`Confirmation required: ${pending.commandId} (${pending.confirmationId})
|
|
56653
|
+
`);
|
|
56654
|
+
if (pending.summary !== void 0) {
|
|
56655
|
+
io.stderr.write(`${redactForLog(pending.summary)}
|
|
56656
|
+
`);
|
|
56657
|
+
}
|
|
56658
|
+
if (pending.riskLevel !== void 0) {
|
|
56659
|
+
io.stderr.write(`Risk level: ${pending.riskLevel}
|
|
56660
|
+
`);
|
|
56661
|
+
}
|
|
56662
|
+
if (pending.diff !== void 0) {
|
|
56663
|
+
io.stderr.write(`Diff:
|
|
56664
|
+
${redactForLog(pending.diff)}
|
|
56665
|
+
`);
|
|
56666
|
+
}
|
|
56164
56667
|
}
|
|
56165
56668
|
function printCommandError2(io, error51) {
|
|
56166
56669
|
if (error51 instanceof CommandClientError && error51.response && !error51.response.ok) {
|