@relayflows/sdk 2.0.21 → 2.0.23
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/agent-artifacts.d.ts +18 -9
- package/dist/agent-artifacts.d.ts.map +1 -1
- package/dist/agent-artifacts.js +27 -11
- package/dist/agent-artifacts.js.map +1 -1
- package/dist/agent-transcript.d.ts +186 -0
- package/dist/agent-transcript.d.ts.map +1 -0
- package/dist/agent-transcript.js +585 -0
- package/dist/agent-transcript.js.map +1 -0
- package/dist/authored-flow-error.d.ts +33 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +20 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +4 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-node-entry.js +5 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +11 -0
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +30 -1
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.js +3 -1
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-source-authority.js +13 -0
- package/dist/authored-source-authority.js.map +1 -1
- package/dist/authored-step-index.d.ts +82 -0
- package/dist/authored-step-index.d.ts.map +1 -0
- package/dist/authored-step-index.js +120 -0
- package/dist/authored-step-index.js.map +1 -0
- package/dist/authored-step-output.d.ts +23 -2
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +47 -5
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-worker-step.d.ts +10 -2
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +81 -7
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +67 -12
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-read.d.ts +31 -0
- package/dist/cli/cloud-read.d.ts.map +1 -0
- package/dist/cli/cloud-read.js +482 -0
- package/dist/cli/cloud-read.js.map +1 -0
- package/dist/cli/run.d.ts +2 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +53 -24
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +43 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +321 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/step-failure.d.ts +26 -0
- package/dist/cli/step-failure.d.ts.map +1 -1
- package/dist/cli/step-failure.js +99 -10
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +41 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +33 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud-http.d.ts +12 -0
- package/dist/cloud-http.d.ts.map +1 -1
- package/dist/cloud-http.js +14 -6
- package/dist/cloud-http.js.map +1 -1
- package/dist/cloud-read.d.ts +154 -0
- package/dist/cloud-read.d.ts.map +1 -0
- package/dist/cloud-read.js +373 -0
- package/dist/cloud-read.js.map +1 -0
- package/dist/cloud-transcript.d.ts +94 -0
- package/dist/cloud-transcript.d.ts.map +1 -0
- package/dist/cloud-transcript.js +342 -0
- package/dist/cloud-transcript.js.map +1 -0
- package/dist/communication/environment.d.ts +3 -0
- package/dist/communication/environment.d.ts.map +1 -0
- package/dist/communication/environment.js +34 -0
- package/dist/communication/environment.js.map +1 -0
- package/dist/communication/history.d.ts +6 -0
- package/dist/communication/history.d.ts.map +1 -0
- package/dist/communication/history.js +38 -0
- package/dist/communication/history.js.map +1 -0
- package/dist/communication/local.d.ts +6 -0
- package/dist/communication/local.d.ts.map +1 -0
- package/dist/communication/local.js +56 -0
- package/dist/communication/local.js.map +1 -0
- package/dist/communication/preflight.d.ts +6 -0
- package/dist/communication/preflight.d.ts.map +1 -0
- package/dist/communication/preflight.js +37 -0
- package/dist/communication/preflight.js.map +1 -0
- package/dist/communication/projection.d.ts +8 -0
- package/dist/communication/projection.d.ts.map +1 -0
- package/dist/communication/projection.js +23 -0
- package/dist/communication/projection.js.map +1 -0
- package/dist/communication/relay.d.ts +78 -0
- package/dist/communication/relay.d.ts.map +1 -0
- package/dist/communication/relay.js +89 -0
- package/dist/communication/relay.js.map +1 -0
- package/dist/communication/session.d.ts +24 -0
- package/dist/communication/session.d.ts.map +1 -0
- package/dist/communication/session.js +100 -0
- package/dist/communication/session.js.map +1 -0
- package/dist/communication/spec.d.ts +20 -0
- package/dist/communication/spec.d.ts.map +1 -0
- package/dist/communication/spec.js +113 -0
- package/dist/communication/spec.js.map +1 -0
- package/dist/communication/tool.d.ts +3 -0
- package/dist/communication/tool.d.ts.map +1 -0
- package/dist/communication/tool.js +23 -0
- package/dist/communication/tool.js.map +1 -0
- package/dist/communication/tools.d.ts +11 -0
- package/dist/communication/tools.d.ts.map +1 -0
- package/dist/communication/tools.js +65 -0
- package/dist/communication/tools.js.map +1 -0
- package/dist/communication/worker.d.ts +6 -0
- package/dist/communication/worker.d.ts.map +1 -0
- package/dist/communication/worker.js +120 -0
- package/dist/communication/worker.js.map +1 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +8 -1
- package/dist/compile.js.map +1 -1
- package/dist/failure-kinds.d.ts +30 -2
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +9 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/journal-client.d.ts +4 -1
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +11 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-reader.d.ts +1 -1
- package/dist/journal-reader.d.ts.map +1 -1
- package/dist/journal-reader.js +3 -1
- package/dist/journal-reader.js.map +1 -1
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +12 -1
- package/dist/llm-worker.js.map +1 -1
- package/dist/permissions-preflight.d.ts +22 -0
- package/dist/permissions-preflight.d.ts.map +1 -0
- package/dist/permissions-preflight.js +49 -0
- package/dist/permissions-preflight.js.map +1 -0
- package/dist/preflight.d.ts +13 -2
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +29 -6
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +39 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/pty-sidechannel.d.ts +7 -0
- package/dist/pty-sidechannel.d.ts.map +1 -1
- package/dist/pty-sidechannel.js.map +1 -1
- package/dist/redact.d.ts +16 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +104 -0
- package/dist/redact.js.map +1 -0
- package/dist/run-state.d.ts +124 -0
- package/dist/run-state.d.ts.map +1 -0
- package/dist/run-state.js +368 -0
- package/dist/run-state.js.map +1 -0
- package/dist/spec.d.ts +24 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/step-env.d.ts +25 -0
- package/dist/step-env.d.ts.map +1 -0
- package/dist/step-env.js +34 -0
- package/dist/step-env.js.map +1 -0
- package/dist/transcript-tail.d.ts +54 -0
- package/dist/transcript-tail.d.ts.map +1 -0
- package/dist/transcript-tail.js +176 -0
- package/dist/transcript-tail.js.map +1 -0
- package/dist/worker-cli.d.ts +10 -0
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +138 -17
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker-usage.d.ts +7 -2
- package/dist/worker-usage.d.ts.map +1 -1
- package/dist/worker-usage.js +21 -3
- package/dist/worker-usage.js.map +1 -1
- package/dist/worker.d.ts +1 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +41 -7
- package/dist/worker.js.map +1 -1
- package/dist/wrapper-runtime.d.ts +7 -1
- package/dist/wrapper-runtime.d.ts.map +1 -1
- package/dist/wrapper-runtime.js +7 -1
- package/dist/wrapper-runtime.js.map +1 -1
- package/dist/wrapper-session.d.ts +8 -0
- package/dist/wrapper-session.d.ts.map +1 -1
- package/dist/wrapper-session.js +156 -23
- package/dist/wrapper-session.js.map +1 -1
- package/package.json +14 -2
- package/src/agent-artifacts.ts +27 -11
- package/src/agent-transcript.ts +651 -0
- package/src/authored-flow-error.ts +17 -0
- package/src/authored-flow-executor.ts +4 -1
- package/src/authored-node-entry.ts +5 -1
- package/src/authored-node-runner.ts +33 -1
- package/src/authored-root.ts +2 -1
- package/src/authored-source-authority.ts +13 -0
- package/src/authored-step-index.ts +157 -0
- package/src/authored-step-output.ts +66 -14
- package/src/authored-worker-step.ts +88 -15
- package/src/cli/check.ts +69 -11
- package/src/cli/cloud-read.ts +518 -0
- package/src/cli/run.ts +43 -24
- package/src/cli/status.ts +349 -0
- package/src/cli/step-failure.ts +100 -10
- package/src/cli-commands.ts +33 -0
- package/src/cli.ts +27 -0
- package/src/cloud-http.ts +32 -6
- package/src/cloud-read.ts +515 -0
- package/src/cloud-transcript.ts +422 -0
- package/src/communication/environment.ts +33 -0
- package/src/communication/history.ts +38 -0
- package/src/communication/local.ts +41 -0
- package/src/communication/preflight.ts +34 -0
- package/src/communication/projection.ts +23 -0
- package/src/communication/relay.ts +102 -0
- package/src/communication/session.ts +84 -0
- package/src/communication/spec.ts +106 -0
- package/src/communication/tool.ts +22 -0
- package/src/communication/tools.ts +50 -0
- package/src/communication/worker.ts +95 -0
- package/src/compile.ts +4 -1
- package/src/failure-kinds.ts +31 -1
- package/src/index.ts +10 -1
- package/src/journal-client.ts +12 -2
- package/src/journal-reader.ts +4 -2
- package/src/llm-worker.ts +12 -1
- package/src/permissions-preflight.ts +53 -0
- package/src/preflight.ts +42 -7
- package/src/protocol.ts +15 -0
- package/src/pty-sidechannel.ts +7 -0
- package/src/redact.ts +106 -0
- package/src/run-state.ts +468 -0
- package/src/spec.ts +18 -1
- package/src/step-env.ts +47 -0
- package/src/transcript-tail.ts +193 -0
- package/src/worker-cli.ts +139 -18
- package/src/worker-usage.ts +26 -3
- package/src/worker.ts +42 -6
- package/src/wrapper-runtime.ts +7 -1
- package/src/wrapper-session.ts +159 -24
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CompiledFlowSpec } from './compile.js';
|
|
2
|
+
import type { PermissionsSpec } from './spec.js';
|
|
3
|
+
import type { PreflightWarning } from './preflight.js';
|
|
4
|
+
|
|
5
|
+
/** Declared fields, reported in this order so two runs read identically. */
|
|
6
|
+
const PERMISSION_FIELDS = ['fileGlobs', 'networkAllowlist', 'accessPreset'] as const;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An agent step's `permissions` block is accepted by gate 1, lowered to
|
|
10
|
+
* `file_globs` / `network_allowlist` / `access_preset`, carried in the kernel
|
|
11
|
+
* step spec and journalled with it. Nothing reads it to gate a file or network
|
|
12
|
+
* access: enforcement is gate 8 (#442). A spec that declares it therefore
|
|
13
|
+
* looks sandboxed and is not, and until this warning existed nothing said so
|
|
14
|
+
* at authoring time.
|
|
15
|
+
*
|
|
16
|
+
* Every defined declaration warns, including `{}` and empty arrays. SURFACE.md
|
|
17
|
+
* accepts those without inferring defaults, and an empty block is exactly as
|
|
18
|
+
* likely to read to an author as "a default sandbox applies". Omitting
|
|
19
|
+
* `permissions` stays silent — there is no belief to correct.
|
|
20
|
+
*
|
|
21
|
+
* Warning-only: `ok` is unaffected, the declaration stays legal, and no value
|
|
22
|
+
* is echoed back (a glob or host is often the least interesting part of the
|
|
23
|
+
* declaration and can be long). The message names which fields were declared
|
|
24
|
+
* so the author can find them, and nothing more.
|
|
25
|
+
*/
|
|
26
|
+
export function permissionsDiagnostics(flow: CompiledFlowSpec): PreflightWarning[] {
|
|
27
|
+
const warnings: PreflightWarning[] = [];
|
|
28
|
+
for (const step of flow.steps) {
|
|
29
|
+
if (step.type !== 'agent' || step.permissions === undefined) continue;
|
|
30
|
+
warnings.push({
|
|
31
|
+
severity: 'warning',
|
|
32
|
+
kind: 'permissions_unenforced',
|
|
33
|
+
stepId: step.id,
|
|
34
|
+
message: permissionsMessage(step.id, step.permissions),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return warnings;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function permissionsMessage(stepId: string, permissions: PermissionsSpec): string {
|
|
41
|
+
const declared = PERMISSION_FIELDS.filter((field) => permissions[field] !== undefined);
|
|
42
|
+
const fields = declared.length === 0 ? '' : ` (${declared.join(', ')})`;
|
|
43
|
+
// `readonly` is the one value whose entire meaning is a guarantee, so it is
|
|
44
|
+
// the one value worth naming: an author reading "cannot write" needs to be
|
|
45
|
+
// told, in the same breath, that today nothing stops a write.
|
|
46
|
+
const preset = permissions.accessPreset === 'readonly'
|
|
47
|
+
? " accessPreset: 'readonly' does not prevent writes."
|
|
48
|
+
: '';
|
|
49
|
+
return `Step "${stepId}" declares permissions${fields}. `
|
|
50
|
+
+ 'This declaration is validated and recorded with the step spec; it is not '
|
|
51
|
+
+ 'currently enforced (gate 8 / #442). These permissions do not restrict file '
|
|
52
|
+
+ `or network access.${preset}`;
|
|
53
|
+
}
|
package/src/preflight.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { communicationInstruction } from './communication/spec.js';
|
|
1
2
|
import { loadPlugins, type LoadedPlugin } from './plugin-loader.js';
|
|
2
3
|
import { PluginError } from './plugin-manifest.js';
|
|
3
4
|
import type { FlowSpec, StepSpec, TriggerSpec, McpServerConfig } from './spec.js';
|
|
4
5
|
import { McpError, openMcpSession, type McpDiagnostic } from './mcp-client.js';
|
|
5
6
|
import { BudgetSyntaxError } from './budget.js';
|
|
6
7
|
import { budgetDiagnostics } from './budget-preflight.js';
|
|
8
|
+
import { permissionsDiagnostics } from './permissions-preflight.js';
|
|
7
9
|
import type { TriggerSource } from '@relayflows/surface';
|
|
8
10
|
import { acceptsAnyOutput, inspectStepGate, type StepGateInspection } from './gate-contract.js';
|
|
9
11
|
import { compileSpec, CompileError } from './compile.js';
|
|
@@ -31,13 +33,24 @@ export interface CliResolution {
|
|
|
31
33
|
|
|
32
34
|
export interface CliProbeResult {
|
|
33
35
|
exists: boolean;
|
|
34
|
-
authenticated: boolean;
|
|
36
|
+
authenticated: boolean | 'unverified';
|
|
35
37
|
/** False when a custom executable did not identify as a wrapper adapter. */
|
|
36
38
|
supported?: boolean;
|
|
37
39
|
/** Exact declared model passed the CLI's model-scoped readiness probe. */
|
|
38
40
|
modelAvailable?: boolean;
|
|
39
41
|
authCommand?: string;
|
|
40
42
|
modelCommand?: string;
|
|
43
|
+
/** Exit code of the authentication probe, when it failed. */
|
|
44
|
+
authExitCode?: number | null;
|
|
45
|
+
/**
|
|
46
|
+
* Redacted output of a FAILED authentication probe.
|
|
47
|
+
*
|
|
48
|
+
* Present only on failure. Without it a `cli_unauthenticated` refusal can
|
|
49
|
+
* state that a probe exited non-zero and never what it said, which makes an
|
|
50
|
+
* intermittent probe failure impossible to tell apart from a genuinely
|
|
51
|
+
* unauthenticated CLI.
|
|
52
|
+
*/
|
|
53
|
+
authFailureDetail?: string;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
export type CliProbeFailureDetail =
|
|
@@ -69,7 +82,7 @@ export interface PreflightProbes {
|
|
|
69
82
|
* model in scope, so readiness answers "can this CLI use THIS model" rather
|
|
70
83
|
* than the weaker "is this CLI authenticated at all".
|
|
71
84
|
*/
|
|
72
|
-
cli(cli: string, source: CliResolutionSource, model?: string): CliProbeResult;
|
|
85
|
+
cli(cli: string, source: CliResolutionSource, model?: string, execution?: 'managed'): CliProbeResult;
|
|
73
86
|
executor(trigger: TriggerSpec): boolean;
|
|
74
87
|
command(binary: string): boolean;
|
|
75
88
|
}
|
|
@@ -223,6 +236,11 @@ function preflightSync(flow: unknown, options: PreflightOptions): PreflightResul
|
|
|
223
236
|
const resolutionByStep = new Map<string, CliResolution>();
|
|
224
237
|
const cliProbeResults = new Map<string, CliProbeOutcome>();
|
|
225
238
|
|
|
239
|
+
// A pure fact about the compiled snapshot, collected before anything that
|
|
240
|
+
// can return early. An unresolved CLI, an unknown model or a bad scope all
|
|
241
|
+
// refuse below without probing, and the author should still be told that the
|
|
242
|
+
// permissions they declared on the same file are not enforced.
|
|
243
|
+
diagnostics.push(...permissionsDiagnostics(compiled));
|
|
226
244
|
diagnostics.push(...scopeDiagnostics(compiled, options));
|
|
227
245
|
for (const server of new Set(options.mcpServers ?? [])) {
|
|
228
246
|
if (options.mcp !== undefined && Object.hasOwn(options.mcp, server)) continue;
|
|
@@ -286,7 +304,8 @@ function preflightSync(flow: unknown, options: PreflightOptions): PreflightResul
|
|
|
286
304
|
continue;
|
|
287
305
|
}
|
|
288
306
|
const resolution = resolutionByStep.get(step.id)!;
|
|
289
|
-
probeResolvedCli(resolution, options.probes, cliProbeResults, diagnostics
|
|
307
|
+
probeResolvedCli(resolution, options.probes, cliProbeResults, diagnostics,
|
|
308
|
+
step.type === 'agent' && communicationInstruction(step.instruction) !== undefined);
|
|
290
309
|
}
|
|
291
310
|
|
|
292
311
|
for (const trigger of compiled.triggers ?? []) {
|
|
@@ -447,6 +466,7 @@ function probeResolvedCli(
|
|
|
447
466
|
probes: PreflightProbes,
|
|
448
467
|
cache: Map<string, CliProbeOutcome>,
|
|
449
468
|
diagnostics: PreflightDiagnostic[],
|
|
469
|
+
managed = false,
|
|
450
470
|
): void {
|
|
451
471
|
// Source is load-bearing: the same relative CLI string resolves from the
|
|
452
472
|
// flow directory for step/named/flow declarations and the config directory for
|
|
@@ -454,11 +474,13 @@ function probeResolvedCli(
|
|
|
454
474
|
// Model is part of the key: the same CLI probed with two different models
|
|
455
475
|
// is two different questions, and caching on the CLI alone would let a
|
|
456
476
|
// model that the CLI cannot resolve inherit an earlier model's pass.
|
|
457
|
-
const cacheKey = JSON.stringify([resolution.cli, resolution.source, resolution.model ?? null]);
|
|
477
|
+
const cacheKey = JSON.stringify([resolution.cli, resolution.source, resolution.model ?? null, managed]);
|
|
458
478
|
let outcome = cache.get(cacheKey);
|
|
459
479
|
if (outcome === undefined) {
|
|
460
480
|
try {
|
|
461
|
-
outcome = { result:
|
|
481
|
+
outcome = { result: managed
|
|
482
|
+
? probes.cli(resolution.cli, resolution.source, resolution.model, 'managed')
|
|
483
|
+
: probes.cli(resolution.cli, resolution.source, resolution.model) };
|
|
462
484
|
} catch (error) {
|
|
463
485
|
const detail = error instanceof CliProbeError ? error.detail : undefined;
|
|
464
486
|
outcome = { failure: detail ?? null };
|
|
@@ -494,14 +516,27 @@ function probeResolvedCli(
|
|
|
494
516
|
cli: resolution.cli,
|
|
495
517
|
message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but it is neither a supported raw Claude/Codex executable nor a conforming Relayflows wrapper; custom wrappers must identify with the relayflows-agent-cli-v1 contract.`,
|
|
496
518
|
});
|
|
497
|
-
} else if (
|
|
519
|
+
} else if (managed && result.authenticated === 'unverified') {
|
|
520
|
+
diagnostics.push({ severity: 'warning', kind: 'managed_cli_unverified', stepId: resolution.stepId,
|
|
521
|
+
message: `Step "${resolution.stepId}" uses Relay's interactive CLI transport. The executable exists, but authentication${resolution.model ? ' and access to model "' + resolution.model + '"' : ''} could not be verified before startup. The managed session must execute its task and report completion; startup or task failure fails the step.` });
|
|
522
|
+
} else if (result.authenticated !== true) {
|
|
498
523
|
const command = result.authCommand ?? `${resolution.cli} auth status`;
|
|
524
|
+
// Say what the probe reported. A refusal that names only the command turns
|
|
525
|
+
// a transient provider rejection and a genuinely unauthenticated CLI into
|
|
526
|
+
// the same message, and the difference decides whether retrying is
|
|
527
|
+
// correct.
|
|
528
|
+
const exitCode = result.authExitCode === undefined || result.authExitCode === null
|
|
529
|
+
? ''
|
|
530
|
+
: ` (exit ${result.authExitCode})`;
|
|
531
|
+
const detail = result.authFailureDetail !== undefined && result.authFailureDetail.length > 0
|
|
532
|
+
? ` It reported: ${result.authFailureDetail}`
|
|
533
|
+
: ' It produced no output, so the reason is unavailable.';
|
|
499
534
|
diagnostics.push({
|
|
500
535
|
severity: 'refusal',
|
|
501
536
|
kind: 'cli_unauthenticated',
|
|
502
537
|
stepId: resolution.stepId,
|
|
503
538
|
cli: resolution.cli,
|
|
504
|
-
message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but "${command}" exited non-zero; authenticate it or repair that adapter's authentication probe
|
|
539
|
+
message: `Step "${resolution.stepId}" declares CLI "${resolution.cli}", but "${command}" exited non-zero${exitCode}; authenticate it or repair that adapter's authentication probe.${detail}`,
|
|
505
540
|
});
|
|
506
541
|
} else if (resolution.model !== undefined && result.modelAvailable !== true) {
|
|
507
542
|
diagnostics.push({
|
package/src/protocol.ts
CHANGED
|
@@ -55,6 +55,9 @@ export type Verb =
|
|
|
55
55
|
| 'step.wait'
|
|
56
56
|
| 'event.emit'
|
|
57
57
|
| 'event.submit'
|
|
58
|
+
| 'channel.append'
|
|
59
|
+
| 'channel.receive'
|
|
60
|
+
| 'channel.ack'
|
|
58
61
|
| 'stream.append'
|
|
59
62
|
| 'stream.read'
|
|
60
63
|
| 'journal.read';
|
|
@@ -160,6 +163,8 @@ export interface WorkerAttachParams {
|
|
|
160
163
|
step_types: StepType[];
|
|
161
164
|
/** Maximum concurrent assignments. Omitted means the conservative default 1. */
|
|
162
165
|
capacity?: number;
|
|
166
|
+
/** Accept only agent steps declaring every named stream (must also be pinned). */
|
|
167
|
+
required_streams?: string[];
|
|
163
168
|
/**
|
|
164
169
|
* The surfaces this worker holds, as opaque revisions/offsets. Required when
|
|
165
170
|
* `step_types` includes `agent` — an agent attempt's start pins come from
|
|
@@ -411,7 +416,17 @@ export interface JournalReadResult {
|
|
|
411
416
|
}
|
|
412
417
|
|
|
413
418
|
/** Typed map of verb -> { params, result }. Used by the client for type-safety. */
|
|
419
|
+
export interface ChannelIdentity {
|
|
420
|
+
run_id: string; step_id: string; attempt: number; idempotency_key: string; channel: string;
|
|
421
|
+
}
|
|
422
|
+
export interface ChannelEntry {
|
|
423
|
+
seq: number;
|
|
424
|
+
payload: { channel: string; offset: number; message?: unknown; producer?: string; consumer?: string; message_id?: string; delivery_seq?: number };
|
|
425
|
+
}
|
|
414
426
|
export interface VerbContract {
|
|
427
|
+
'channel.append': { params: ChannelIdentity & { message_id: string; message: unknown }; result: ChannelEntry };
|
|
428
|
+
'channel.receive': { params: ChannelIdentity; result: ChannelEntry | null };
|
|
429
|
+
'channel.ack': { params: ChannelIdentity & { delivery_seq: number }; result: ChannelEntry };
|
|
415
430
|
hello: { params: HelloParams; result: HelloResult };
|
|
416
431
|
'run.start': { params: RunStartParams; result: RunStartResult };
|
|
417
432
|
'run.resume': { params: RunResumeParams; result: RunResumeResult };
|
package/src/pty-sidechannel.ts
CHANGED
|
@@ -6,6 +6,13 @@ export interface SidechannelContext {
|
|
|
6
6
|
dataDir: string;
|
|
7
7
|
runId: string;
|
|
8
8
|
stepId: string;
|
|
9
|
+
/**
|
|
10
|
+
* Which attempt this is. Names the per-attempt transcript file
|
|
11
|
+
* (`agent-transcript.ts`) and the transcript tails (`transcript-tail.ts`),
|
|
12
|
+
* and is exported into the agent's environment. Optional: without it there
|
|
13
|
+
* is no attempt to name, so neither file is written.
|
|
14
|
+
*/
|
|
15
|
+
attempt?: number;
|
|
9
16
|
onReady?: (path: string) => void;
|
|
10
17
|
onDrive: () => void;
|
|
11
18
|
}
|
package/src/redact.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Redaction for free text an agent-facing surface may print.
|
|
2
|
+
//
|
|
3
|
+
// `flows status` renders gate `detail`, transcript tails and error messages
|
|
4
|
+
// to a process that may be an agent, in a sandbox whose stdout is captured.
|
|
5
|
+
// A direct agent inherits the worker's whole environment (worker-cli.ts), so
|
|
6
|
+
// anything the operator exported can be echoed back into that text. This
|
|
7
|
+
// module is the one place that decides what never reaches the page.
|
|
8
|
+
//
|
|
9
|
+
// Applied to free text only. Identifiers — run ids, step ids, hashes and env
|
|
10
|
+
// NAMES — are never rewritten, so the view stays greppable.
|
|
11
|
+
|
|
12
|
+
/** Env names whose values are treated as secret when they are long enough. */
|
|
13
|
+
const SECRET_NAME = /TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL|AUTH/i;
|
|
14
|
+
|
|
15
|
+
/** Shorter values are flags and modes (`AUTH_MODE=off`), not material. */
|
|
16
|
+
const MIN_SECRET_LENGTH = 8;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Token shapes replaced wholesale. The first is the relay token family
|
|
20
|
+
* `redactRelayError` in worker-cli.ts already scrubbed; the rest are the
|
|
21
|
+
* bearer, header and vendor shapes a gate render can carry.
|
|
22
|
+
*/
|
|
23
|
+
const TOKEN_PATTERNS: readonly RegExp[] = [
|
|
24
|
+
/\b(?:at|rk|nt|ot|br|arr)_(?:live_)?[A-Za-z0-9_-]+/g,
|
|
25
|
+
/\bsk-[A-Za-z0-9_-]{16,}/g,
|
|
26
|
+
/\bghp_[A-Za-z0-9]{20,}/g,
|
|
27
|
+
/\bxox[abp]-[A-Za-z0-9-]+/g,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Header and assignment shapes: keep the name (and an auth scheme), replace
|
|
32
|
+
* the value. A value already replaced by an earlier pass is left as it is,
|
|
33
|
+
* so the reader still sees which env name it came from.
|
|
34
|
+
*/
|
|
35
|
+
const NAMED_VALUE_PATTERNS: readonly RegExp[] = [
|
|
36
|
+
/\b(authorization:\s*(?:\w+\s+)?)(?!\[redacted)\S+/gi,
|
|
37
|
+
/\b(Bearer\s+)(?!\[redacted)\S+/g,
|
|
38
|
+
/\b(x-callback-token:\s*)(?!\[redacted)\S+/gi,
|
|
39
|
+
/\b(x-nightcto-evidence-token:\s*)(?!\[redacted)\S+/gi,
|
|
40
|
+
/\b([A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD)=)(?!\[redacted)\S+/gi,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Credential fields in serialized JSON. A gate detail or a transcript line
|
|
45
|
+
* often carries a request or config body, where the value is an opaque string
|
|
46
|
+
* that matches no vendor token shape and — for anything this process did not
|
|
47
|
+
* export — no environment value either: `{"authToken":"opaque-secret"}` went
|
|
48
|
+
* through untouched. The field name is kept, so the reader knows what was cut.
|
|
49
|
+
*
|
|
50
|
+
* Quoted string values only — including escaped ones: a serialized private
|
|
51
|
+
* key or nested JSON reaches these fields as `\n` and `\"`, and a value
|
|
52
|
+
* class that stopped at the first backslash left every one of them intact.
|
|
53
|
+
* Only where the field NAME ends in a credential noun. Containing one is not enough: `monkey` and `keyboard` are
|
|
54
|
+
* ordinary fields, so the name is split on separators and camelCase humps and
|
|
55
|
+
* the last word decides.
|
|
56
|
+
*/
|
|
57
|
+
const JSON_STRING_FIELD = /("([A-Za-z0-9_.\-]{1,64})"\s*:\s*")(?!\[redacted)((?:[^"\\]|\\.){4,})(")/g;
|
|
58
|
+
const CREDENTIAL_NOUN = /^(?:token|secret|key|password|passwd|credential|cookie)s?$/i;
|
|
59
|
+
|
|
60
|
+
function isCredentialName(name: string): boolean {
|
|
61
|
+
if (name.toLowerCase() === 'authorization') return true;
|
|
62
|
+
const words = name.split(/[_\-.]+/).flatMap((word) => word.split(/(?<=[a-z0-9])(?=[A-Z])/u));
|
|
63
|
+
const last = words.at(-1);
|
|
64
|
+
return last !== undefined && CREDENTIAL_NOUN.test(last);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Secret env values, longest first so a value that contains another is replaced whole. */
|
|
68
|
+
function secretEnvValues(env: NodeJS.ProcessEnv): Array<[name: string, value: string]> {
|
|
69
|
+
const secrets: Array<[string, string]> = [];
|
|
70
|
+
for (const [name, value] of Object.entries(env)) {
|
|
71
|
+
if (typeof value !== 'string' || value.length < MIN_SECRET_LENGTH) continue;
|
|
72
|
+
if (SECRET_NAME.test(name)) secrets.push([name, value]);
|
|
73
|
+
}
|
|
74
|
+
return secrets.sort((a, b) => b[1].length - a[1].length);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Scrub secret env values and known token shapes out of `text`.
|
|
79
|
+
*
|
|
80
|
+
* An env value is replaced by `[redacted:<NAME>]` so the reader learns which
|
|
81
|
+
* variable leaked without learning its value. Token shapes are replaced by
|
|
82
|
+
* `[redacted]`; header and `NAME=value` shapes keep their name.
|
|
83
|
+
*/
|
|
84
|
+
export function redact(text: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
85
|
+
let out = text;
|
|
86
|
+
for (const [name, value] of secretEnvValues(env)) out = out.replaceAll(value, `[redacted:${name}]`);
|
|
87
|
+
for (const pattern of TOKEN_PATTERNS) out = out.replace(pattern, '[redacted]');
|
|
88
|
+
for (const pattern of NAMED_VALUE_PATTERNS) out = out.replace(pattern, '$1[redacted]');
|
|
89
|
+
out = out.replace(JSON_STRING_FIELD, (match, open: string, name: string, _value: string, close: string) =>
|
|
90
|
+
isCredentialName(name) ? `${open}[redacted]${close}` : match);
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The relay-transport error scrub `worker-cli.ts` has always applied: the two
|
|
96
|
+
* relay credential names by exact match, at any length, spelled `[redacted]`.
|
|
97
|
+
* Everything `redact` adds is applied after, so the historical output for
|
|
98
|
+
* those two names and the token pattern is unchanged.
|
|
99
|
+
*/
|
|
100
|
+
export function redactRelayError(message: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
101
|
+
for (const key of ['RELAY_AGENT_TOKEN', 'RELAY_API_KEY']) {
|
|
102
|
+
const secret = env[key];
|
|
103
|
+
if (secret) message = message.replaceAll(secret, '[redacted]');
|
|
104
|
+
}
|
|
105
|
+
return redact(message, env);
|
|
106
|
+
}
|