@namzu/cli 10.0.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -46
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +133 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +29 -0
- package/dist/commands/acp.d.ts.map +1 -0
- package/dist/commands/acp.js +156 -0
- package/dist/commands/acp.js.map +1 -0
- package/dist/commands/doctor.d.ts +12 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +18 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/drain.d.ts +2 -2
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +31 -8
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/run-flags.d.ts.map +1 -1
- package/dist/commands/run-flags.js +33 -0
- package/dist/commands/run-flags.js.map +1 -1
- package/dist/commands/run-stream.d.ts +3 -3
- package/dist/commands/run-stream.d.ts.map +1 -1
- package/dist/commands/run-stream.js +27 -10
- package/dist/commands/run-stream.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +50 -4
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/types.d.ts +10 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config/load.d.ts +64 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +156 -20
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +88 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/capabilities.d.ts +88 -0
- package/dist/context/capabilities.d.ts.map +1 -0
- package/dist/context/capabilities.js +160 -0
- package/dist/context/capabilities.js.map +1 -0
- package/dist/context/sandbox.d.ts +54 -0
- package/dist/context/sandbox.d.ts.map +1 -0
- package/dist/context/sandbox.js +61 -0
- package/dist/context/sandbox.js.map +1 -0
- package/dist/doctor/checks/index.d.ts +6 -1
- package/dist/doctor/checks/index.d.ts.map +1 -1
- package/dist/doctor/checks/index.js +45 -2
- package/dist/doctor/checks/index.js.map +1 -1
- package/dist/doctor/checks/invariants.d.ts +23 -0
- package/dist/doctor/checks/invariants.d.ts.map +1 -0
- package/dist/doctor/checks/invariants.js +79 -0
- package/dist/doctor/checks/invariants.js.map +1 -0
- package/dist/doctor/checks/logging.d.ts +10 -0
- package/dist/doctor/checks/logging.d.ts.map +1 -0
- package/dist/doctor/checks/logging.js +69 -0
- package/dist/doctor/checks/logging.js.map +1 -0
- package/dist/doctor/checks/session-export.d.ts +21 -0
- package/dist/doctor/checks/session-export.d.ts.map +1 -0
- package/dist/doctor/checks/session-export.js +62 -0
- package/dist/doctor/checks/session-export.js.map +1 -0
- package/dist/doctor/checks/telemetry.d.ts +15 -28
- package/dist/doctor/checks/telemetry.d.ts.map +1 -1
- package/dist/doctor/checks/telemetry.js +34 -54
- package/dist/doctor/checks/telemetry.js.map +1 -1
- package/dist/doctor/checks/vault.d.ts +4 -20
- package/dist/doctor/checks/vault.d.ts.map +1 -1
- package/dist/doctor/checks/vault.js +58 -19
- package/dist/doctor/checks/vault.js.map +1 -1
- package/dist/doctor/registry.d.ts.map +1 -1
- package/dist/doctor/registry.js +13 -6
- package/dist/doctor/registry.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/files/attachment-store.d.ts +44 -0
- package/dist/integrations/files/attachment-store.d.ts.map +1 -0
- package/dist/integrations/files/attachment-store.js +86 -0
- package/dist/integrations/files/attachment-store.js.map +1 -0
- package/dist/integrations/providers/credential-provider.d.ts +39 -0
- package/dist/integrations/providers/credential-provider.d.ts.map +1 -0
- package/dist/integrations/providers/credential-provider.js +74 -0
- package/dist/integrations/providers/credential-provider.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +15 -3
- package/dist/integrations/providers/discover.js.map +1 -1
- package/dist/integrations/providers/oauth.d.ts.map +1 -1
- package/dist/integrations/providers/oauth.js +24 -1
- package/dist/integrations/providers/oauth.js.map +1 -1
- package/dist/integrations/sessions/store.d.ts +2 -2
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +21 -9
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +3 -3
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +11 -11
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/telemetry/session-export.d.ts +99 -0
- package/dist/integrations/telemetry/session-export.d.ts.map +1 -0
- package/dist/integrations/telemetry/session-export.js +119 -0
- package/dist/integrations/telemetry/session-export.js.map +1 -0
- package/dist/logging.d.ts +79 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +67 -0
- package/dist/logging.js.map +1 -0
- package/dist/permissions/rules.d.ts +3 -3
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +1 -1
- package/dist/permissions/rules.js.map +1 -1
- package/dist/skills/store.d.ts +1 -1
- package/dist/skills/store.js +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +93 -6
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/StatusBar.d.ts +1 -1
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/agent.d.ts +65 -24
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +337 -122
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +17 -6
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/log-pane.d.ts +48 -0
- package/dist/tui/log-pane.d.ts.map +1 -0
- package/dist/tui/log-pane.js +106 -0
- package/dist/tui/log-pane.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +134 -11
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +169 -15
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/types.d.ts +18 -2
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +8 -6
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Logger, type SandboxProvider } from '@namzu/sdk';
|
|
2
|
+
import type { SandboxConfig } from '../config/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* The sandbox a CLI run executes its commands in.
|
|
5
|
+
*
|
|
6
|
+
* There was none. `sandboxProvider` appeared zero times in this package,
|
|
7
|
+
* so `context.sandbox` was always undefined and `BashTool` took its
|
|
8
|
+
* fallback branch — `execAsync` in the host process, with the host
|
|
9
|
+
* environment, meaning every credential the operator's shell holds went
|
|
10
|
+
* to every command the model chose to run. The isolation the docs
|
|
11
|
+
* described held on no path.
|
|
12
|
+
*
|
|
13
|
+
* On by default now. What it enforces is a property of the machine, and
|
|
14
|
+
* this module's other job is to make sure nobody has to guess which.
|
|
15
|
+
*/
|
|
16
|
+
export interface ResolvedSandbox {
|
|
17
|
+
/** Absent when the operator turned it off. */
|
|
18
|
+
readonly provider?: SandboxProvider;
|
|
19
|
+
/** One line for the operator, always — including when it is off. */
|
|
20
|
+
readonly notice: string;
|
|
21
|
+
/**
|
|
22
|
+
* True when commands run on the host with no confinement.
|
|
23
|
+
*
|
|
24
|
+
* Either because the sandbox is off, or because this platform's
|
|
25
|
+
* environment enforces nothing. A caller that wants to warn louder in
|
|
26
|
+
* that case does not have to re-derive it from the notice text.
|
|
27
|
+
*/
|
|
28
|
+
readonly unconfined: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the sandbox for a run, or explain why there is none.
|
|
32
|
+
*
|
|
33
|
+
* Never throws for an ordinary platform shortfall — a machine that cannot
|
|
34
|
+
* confine the network still runs the CLI, and says so. It DOES throw when
|
|
35
|
+
* the operator named a control under `requireIsolation` that this machine
|
|
36
|
+
* cannot enforce, because that request is the one case where continuing
|
|
37
|
+
* quietly would be answering a question they asked with a different
|
|
38
|
+
* answer than the true one.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveSandbox(log: Logger, config: SandboxConfig | undefined): ResolvedSandbox;
|
|
41
|
+
/**
|
|
42
|
+
* `namzu.sandbox.resolved`'s severity: `warn` when nothing is confined,
|
|
43
|
+
* `info` otherwise. Extracted from the emit call site (`tui/agent.ts`) so
|
|
44
|
+
* this mapping — the one that decides whether an unconfined sandbox reads
|
|
45
|
+
* as routine or as something an operator should act on — is testable
|
|
46
|
+
* without depending on which isolation tier the test happens to run under.
|
|
47
|
+
* `resolveSandbox` reports whatever THIS machine actually enforces, and
|
|
48
|
+
* CI's machine is not every reader's; the existing tests in this file
|
|
49
|
+
* already work around that by branching at runtime on `resolved.unconfined`
|
|
50
|
+
* rather than asserting a fixed tier. A pure function over `ResolvedSandbox`
|
|
51
|
+
* sidesteps the need for that workaround entirely for this one property.
|
|
52
|
+
*/
|
|
53
|
+
export declare function sandboxResolvedSeverity(sandbox: ResolvedSandbox): 'info' | 'warn';
|
|
54
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/context/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,MAAM,EAGX,KAAK,eAAe,EAEpB,MAAM,YAAY,CAAA;AAEnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;IACnC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,SAAS,GAAG,eAAe,CAuC9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,MAAM,CAEjF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { LocalSandboxProvider, SANDBOX_ISOLATION_CONTROLS, isolationOf, } from '@namzu/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Build the sandbox for a run, or explain why there is none.
|
|
4
|
+
*
|
|
5
|
+
* Never throws for an ordinary platform shortfall — a machine that cannot
|
|
6
|
+
* confine the network still runs the CLI, and says so. It DOES throw when
|
|
7
|
+
* the operator named a control under `requireIsolation` that this machine
|
|
8
|
+
* cannot enforce, because that request is the one case where continuing
|
|
9
|
+
* quietly would be answering a question they asked with a different
|
|
10
|
+
* answer than the true one.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveSandbox(log, config) {
|
|
13
|
+
if (config?.enabled === false) {
|
|
14
|
+
return {
|
|
15
|
+
notice: "Sandbox off by configuration: commands run in this process, with this shell's environment. Remove `sandbox.enabled: false` to turn it back on.",
|
|
16
|
+
unconfined: true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const required = (config?.requireIsolation ?? []);
|
|
20
|
+
// Constructing with the requirement is what makes `requireIsolation`
|
|
21
|
+
// mean something: the provider refuses rather than downgrading, and the
|
|
22
|
+
// refusal names the control. Catching it here would turn a stated
|
|
23
|
+
// requirement back into a preference.
|
|
24
|
+
const provider = new LocalSandboxProvider(log, { requireIsolation: required });
|
|
25
|
+
const report = isolationOf(provider.environment);
|
|
26
|
+
const enforced = SANDBOX_ISOLATION_CONTROLS.filter((c) => report[c]);
|
|
27
|
+
const missing = SANDBOX_ISOLATION_CONTROLS.filter((c) => !report[c]);
|
|
28
|
+
if (enforced.length === 0) {
|
|
29
|
+
// The honest case, and the one most likely to be misread. The
|
|
30
|
+
// sandbox is attached and confines nothing, which is not the same as
|
|
31
|
+
// no sandbox and is emphatically not protection.
|
|
32
|
+
return {
|
|
33
|
+
provider,
|
|
34
|
+
notice: `Sandbox on (${provider.environment}), but this platform enforces none of ${SANDBOX_ISOLATION_CONTROLS.join(', ')} — commands are not confined. Name what you need under \`sandbox.requireIsolation\` to be refused instead of surprised.`,
|
|
35
|
+
unconfined: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
provider,
|
|
40
|
+
notice: missing.length === 0
|
|
41
|
+
? `Sandbox on (${provider.environment}): enforcing ${enforced.join(', ')}.`
|
|
42
|
+
: `Sandbox on (${provider.environment}): enforcing ${enforced.join(', ')}; NOT enforcing ${missing.join(', ')}.`,
|
|
43
|
+
unconfined: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `namzu.sandbox.resolved`'s severity: `warn` when nothing is confined,
|
|
48
|
+
* `info` otherwise. Extracted from the emit call site (`tui/agent.ts`) so
|
|
49
|
+
* this mapping — the one that decides whether an unconfined sandbox reads
|
|
50
|
+
* as routine or as something an operator should act on — is testable
|
|
51
|
+
* without depending on which isolation tier the test happens to run under.
|
|
52
|
+
* `resolveSandbox` reports whatever THIS machine actually enforces, and
|
|
53
|
+
* CI's machine is not every reader's; the existing tests in this file
|
|
54
|
+
* already work around that by branching at runtime on `resolved.unconfined`
|
|
55
|
+
* rather than asserting a fixed tier. A pure function over `ResolvedSandbox`
|
|
56
|
+
* sidesteps the need for that workaround entirely for this one property.
|
|
57
|
+
*/
|
|
58
|
+
export function sandboxResolvedSeverity(sandbox) {
|
|
59
|
+
return sandbox.unconfined ? 'warn' : 'info';
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/context/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,oBAAoB,EAEpB,0BAA0B,EAG1B,WAAW,GACX,MAAM,YAAY,CAAA;AAiCnB;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,MAAiC;IAC5E,IAAI,MAAM,EAAE,OAAO,KAAK,KAAK,EAAE,CAAC;QAC/B,OAAO;YACN,MAAM,EACL,gJAAgJ;YACjJ,UAAU,EAAE,IAAI;SAChB,CAAA;IACF,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,gBAAgB,IAAI,EAAE,CAAuC,CAAA;IACvF,qEAAqE;IACrE,wEAAwE;IACxE,kEAAkE;IAClE,sCAAsC;IACtC,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CAAC,GAAG,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAC,CAAA;IAE9E,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IAChD,MAAM,QAAQ,GAAG,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;IACpE,MAAM,OAAO,GAAG,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;IAEpE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,8DAA8D;QAC9D,qEAAqE;QACrE,iDAAiD;QACjD,OAAO;YACN,QAAQ;YACR,MAAM,EAAE,eAAe,QAAQ,CAAC,WAAW,yCAAyC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,yHAAyH;YAClP,UAAU,EAAE,IAAI;SAChB,CAAA;IACF,CAAC;IAED,OAAO;QACN,QAAQ;QACR,MAAM,EACL,OAAO,CAAC,MAAM,KAAK,CAAC;YACnB,CAAC,CAAC,eAAe,QAAQ,CAAC,WAAW,gBAAgB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC3E,CAAC,CAAC,eAAe,QAAQ,CAAC,WAAW,gBAAgB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAClH,UAAU,EAAE,KAAK;KACjB,CAAA;AACF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAwB;IAC/D,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;AAC5C,CAAC"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { DoctorCheck } from '@namzu/sdk';
|
|
2
2
|
import { providerChainCheck } from './chain.js';
|
|
3
3
|
import { credentialSourcesCheck } from './credentials.js';
|
|
4
|
+
import { invariantsCheck } from './invariants.js';
|
|
5
|
+
import { loggingPipelineCheck } from './logging.js';
|
|
4
6
|
import { providersRegisteredCheck } from './providers.js';
|
|
5
7
|
import { cwdWritableCheck, tmpdirWritableCheck } from './runtime.js';
|
|
6
8
|
import { sandboxPlatformCheck } from './sandbox.js';
|
|
7
9
|
import { telemetryInstalledCheck } from './telemetry.js';
|
|
8
10
|
import { vaultRegisteredCheck } from './vault.js';
|
|
9
|
-
export
|
|
11
|
+
export declare const sandboxInstalledCheck: DoctorCheck;
|
|
12
|
+
export declare const filesInstalledCheck: DoctorCheck;
|
|
13
|
+
export declare const computerUseInstalledCheck: DoctorCheck;
|
|
14
|
+
export { credentialSourcesCheck, providerChainCheck, providersRegisteredCheck, cwdWritableCheck, tmpdirWritableCheck, invariantsCheck, loggingPipelineCheck, sandboxPlatformCheck, telemetryInstalledCheck, vaultRegisteredCheck, };
|
|
10
15
|
export declare const builtInDoctorChecks: readonly DoctorCheck[];
|
|
11
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAA;AAGhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAA4B,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AA2BjD,eAAO,MAAM,qBAAqB,EAAE,WAAyD,CAAA;AAC7F,eAAO,MAAM,mBAAmB,EAAE,WAAsD,CAAA;AACxF,eAAO,MAAM,yBAAyB,EAAE,WAGvC,CAAA;AAOD,OAAO,EACN,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,GACpB,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,SAAS,WAAW,EAoBrD,CAAA"}
|
|
@@ -1,11 +1,46 @@
|
|
|
1
|
+
import { capabilityCheckId } from '../../context/capabilities.js';
|
|
1
2
|
import { providerChainCheck } from './chain.js';
|
|
2
3
|
import { credentialSourcesCheck } from './credentials.js';
|
|
4
|
+
import { invariantsCheck } from './invariants.js';
|
|
5
|
+
import { loggingPipelineCheck } from './logging.js';
|
|
3
6
|
import { providersRegisteredCheck } from './providers.js';
|
|
4
7
|
import { cwdWritableCheck, tmpdirWritableCheck } from './runtime.js';
|
|
5
8
|
import { sandboxPlatformCheck } from './sandbox.js';
|
|
6
|
-
import { telemetryInstalledCheck } from './telemetry.js';
|
|
9
|
+
import { describeInstalledPackage, telemetryInstalledCheck } from './telemetry.js';
|
|
7
10
|
import { vaultRegisteredCheck } from './vault.js';
|
|
8
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Build an `installed` check for one optional package, over the same
|
|
13
|
+
* absent/present/broken mapping `telemetryInstalledCheck` already used.
|
|
14
|
+
*
|
|
15
|
+
* `telemetryInstalledCheck` keeps its own hand-written definition below
|
|
16
|
+
* rather than being rebuilt through this factory — it shipped first, and
|
|
17
|
+
* `doctor/checks/__tests__/telemetry.test.ts` already exercises that exact
|
|
18
|
+
* object with zero edits required by NZ-BOOT-02. A fifth optional package
|
|
19
|
+
* needs one call to this factory, not a fourth hand-written check that can
|
|
20
|
+
* drift from the other three.
|
|
21
|
+
*/
|
|
22
|
+
function installedCheck(specifier, category) {
|
|
23
|
+
return {
|
|
24
|
+
id: capabilityCheckId(specifier),
|
|
25
|
+
category,
|
|
26
|
+
run: () => describeInstalledPackage(specifier),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// `sandboxPlatformCheck` already owns the `sandbox` category for a different
|
|
30
|
+
// question (does THIS PLATFORM enforce isolation); this check asking IS THE
|
|
31
|
+
// PACKAGE HERE fits the same category. `DoctorCategory` has no `files` or
|
|
32
|
+
// `computer-use` member, and widening that union is an SDK type change this
|
|
33
|
+
// task does not make, so those two register under `custom` — the category
|
|
34
|
+
// the registry's own tests already use for exactly this situation.
|
|
35
|
+
export const sandboxInstalledCheck = installedCheck('@namzu/sandbox', 'sandbox');
|
|
36
|
+
export const filesInstalledCheck = installedCheck('@namzu/files', 'custom');
|
|
37
|
+
export const computerUseInstalledCheck = installedCheck('@namzu/computer-use', 'custom');
|
|
38
|
+
// NOTE the fix versus the original submission: sandboxInstalledCheck /
|
|
39
|
+
// filesInstalledCheck / computerUseInstalledCheck are NOT repeated here.
|
|
40
|
+
// They already carry their own `export` keyword above; naming them again in
|
|
41
|
+
// this aggregation block is a duplicate-export declaration and fails
|
|
42
|
+
// `tsc --build` with TS2323/TS2484 (confirmed by building this exact file).
|
|
43
|
+
export { credentialSourcesCheck, providerChainCheck, providersRegisteredCheck, cwdWritableCheck, tmpdirWritableCheck, invariantsCheck, loggingPipelineCheck, sandboxPlatformCheck, telemetryInstalledCheck, vaultRegisteredCheck, };
|
|
9
44
|
export const builtInDoctorChecks = [
|
|
10
45
|
sandboxPlatformCheck,
|
|
11
46
|
cwdWritableCheck,
|
|
@@ -17,6 +52,14 @@ export const builtInDoctorChecks = [
|
|
|
17
52
|
// the operator has seen what was found.
|
|
18
53
|
providerChainCheck,
|
|
19
54
|
vaultRegisteredCheck,
|
|
55
|
+
sandboxInstalledCheck,
|
|
56
|
+
filesInstalledCheck,
|
|
57
|
+
computerUseInstalledCheck,
|
|
20
58
|
telemetryInstalledCheck,
|
|
59
|
+
// Last, deliberately: it reports what the pipeline did to the records
|
|
60
|
+
// every check above it just produced, so running it first would report a
|
|
61
|
+
// process that had barely logged anything yet.
|
|
62
|
+
loggingPipelineCheck,
|
|
63
|
+
invariantsCheck,
|
|
21
64
|
];
|
|
22
65
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/doctor/checks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/doctor/checks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;;;GAUG;AACH,SAAS,cAAc,CAAC,SAAiB,EAAE,QAAiC;IAC3E,OAAO;QACN,EAAE,EAAE,iBAAiB,CAAC,SAAS,CAAC;QAChC,QAAQ;QACR,GAAG,EAAE,GAA+B,EAAE,CAAC,wBAAwB,CAAC,SAAS,CAAC;KAC1E,CAAA;AACF,CAAC;AAED,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,4EAA4E;AAC5E,0EAA0E;AAC1E,mEAAmE;AACnE,MAAM,CAAC,MAAM,qBAAqB,GAAgB,cAAc,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAA;AAC7F,MAAM,CAAC,MAAM,mBAAmB,GAAgB,cAAc,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAA;AACxF,MAAM,CAAC,MAAM,yBAAyB,GAAgB,cAAc,CACnE,qBAAqB,EACrB,QAAQ,CACR,CAAA;AAED,uEAAuE;AACvE,yEAAyE;AACzE,4EAA4E;AAC5E,qEAAqE;AACrE,4EAA4E;AAC5E,OAAO,EACN,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,GACpB,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAA2B;IAC1D,oBAAoB;IACpB,gBAAgB;IAChB,mBAAmB;IACnB,wBAAwB;IACxB,sBAAsB;IACtB,4EAA4E;IAC5E,0EAA0E;IAC1E,wCAAwC;IACxC,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACrB,mBAAmB;IACnB,yBAAyB;IACzB,uBAAuB;IACvB,sEAAsE;IACtE,yEAAyE;IACzE,+CAA+C;IAC/C,oBAAoB;IACpB,eAAe;CACf,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DoctorCheck, DoctorCheckResult, InvariantRegistry } from '@namzu/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Evaluate every invariant `registry` holds and fold the outcomes, plus each
|
|
4
|
+
* invariant's own violation counter, into one doctor row.
|
|
5
|
+
*
|
|
6
|
+
* Exported separately from the `DoctorCheck` — same reasoning as
|
|
7
|
+
* `describeInstalledPackage` on the telemetry check — so a test can drive a
|
|
8
|
+
* SCOPED registry directly, rather than mutating the process-wide singleton
|
|
9
|
+
* every other test also reads. The check itself is still exercised through
|
|
10
|
+
* `invariantsCheck.run`, because a helper proven in isolation says nothing
|
|
11
|
+
* about whether the check reaches it.
|
|
12
|
+
*
|
|
13
|
+
* Every invariant is evaluated with `ctx: undefined`. `namzu doctor` runs
|
|
14
|
+
* with no live run and no candidate compaction reduction — there is nothing
|
|
15
|
+
* a real invariant's check could read that is specific to one run — so
|
|
16
|
+
* `undefined` is the honest context, not a shortcut: each check registered
|
|
17
|
+
* against it is written to answer `unknown` rather than guess (see
|
|
18
|
+
* `compaction.ts` and `claim-disk.ts` in `@namzu/sdk`), and this row reports
|
|
19
|
+
* that state rather than papering over it as a pass.
|
|
20
|
+
*/
|
|
21
|
+
export declare function describeInvariants(registry: InvariantRegistry): Promise<DoctorCheckResult>;
|
|
22
|
+
export declare const invariantsCheck: DoctorCheck;
|
|
23
|
+
//# sourceMappingURL=invariants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariants.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/invariants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAanF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAyDhG;AAED,eAAO,MAAM,eAAe,EAAE,WAI7B,CAAA"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { invariants } from '@namzu/sdk';
|
|
2
|
+
function label(row) {
|
|
3
|
+
return `${row.id} (${row.violations} violation${row.violations === 1 ? '' : 's'})`;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Evaluate every invariant `registry` holds and fold the outcomes, plus each
|
|
7
|
+
* invariant's own violation counter, into one doctor row.
|
|
8
|
+
*
|
|
9
|
+
* Exported separately from the `DoctorCheck` — same reasoning as
|
|
10
|
+
* `describeInstalledPackage` on the telemetry check — so a test can drive a
|
|
11
|
+
* SCOPED registry directly, rather than mutating the process-wide singleton
|
|
12
|
+
* every other test also reads. The check itself is still exercised through
|
|
13
|
+
* `invariantsCheck.run`, because a helper proven in isolation says nothing
|
|
14
|
+
* about whether the check reaches it.
|
|
15
|
+
*
|
|
16
|
+
* Every invariant is evaluated with `ctx: undefined`. `namzu doctor` runs
|
|
17
|
+
* with no live run and no candidate compaction reduction — there is nothing
|
|
18
|
+
* a real invariant's check could read that is specific to one run — so
|
|
19
|
+
* `undefined` is the honest context, not a shortcut: each check registered
|
|
20
|
+
* against it is written to answer `unknown` rather than guess (see
|
|
21
|
+
* `compaction.ts` and `claim-disk.ts` in `@namzu/sdk`), and this row reports
|
|
22
|
+
* that state rather than papering over it as a pass.
|
|
23
|
+
*/
|
|
24
|
+
export async function describeInvariants(registry) {
|
|
25
|
+
const ids = registry.listIds();
|
|
26
|
+
if (ids.length === 0) {
|
|
27
|
+
return { status: 'skipped', message: 'no invariants registered' };
|
|
28
|
+
}
|
|
29
|
+
const rows = await Promise.all(ids.map(async (id) => ({
|
|
30
|
+
id,
|
|
31
|
+
outcome: await registry.evaluate(id, undefined),
|
|
32
|
+
violations: registry.violationCount(id),
|
|
33
|
+
})));
|
|
34
|
+
const violated = rows.filter((r) => r.outcome.state === 'violated');
|
|
35
|
+
const unknown = rows.filter((r) => r.outcome.state === 'unknown');
|
|
36
|
+
if (violated.length > 0) {
|
|
37
|
+
const detail = violated
|
|
38
|
+
.map((r) => (r.outcome.state === 'violated' ? `${label(r)}: ${r.outcome.detail}` : label(r)))
|
|
39
|
+
.join('; ');
|
|
40
|
+
return {
|
|
41
|
+
status: 'fail',
|
|
42
|
+
message: `${violated.length}/${ids.length} invariant(s) violated — ${detail}`,
|
|
43
|
+
remediation: 'Check the warn/error logs at the module that registered the violated invariant.',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (unknown.length > 0) {
|
|
47
|
+
const names = unknown.map(label).join(', ');
|
|
48
|
+
// `skipped`, not `inconclusive`, and the difference is the whole
|
|
49
|
+
// behaviour of this row. `inconclusive` maps to exit 69 in
|
|
50
|
+
// `doctor/registry.ts` and means "I asked and could not establish an
|
|
51
|
+
// answer" — a real signal that something is wrong with the check
|
|
52
|
+
// itself. A runtime invariant outside a live run is a different thing:
|
|
53
|
+
// the question has no subject yet, every registered check is written
|
|
54
|
+
// to answer `unknown` here on purpose, and so this state is reached on
|
|
55
|
+
// EVERY doctor run. Reporting it as inconclusive made `namzu doctor`
|
|
56
|
+
// exit 69 unconditionally, which retires the one code that was
|
|
57
|
+
// supposed to mean something.
|
|
58
|
+
//
|
|
59
|
+
// `skipped` is not "satisfied" either — the summary counts it
|
|
60
|
+
// separately from `pass`, and the message still names which invariants
|
|
61
|
+
// went unevaluated and how many violations each has accumulated. The
|
|
62
|
+
// violation counters above are what actually carry a live run's
|
|
63
|
+
// findings back to this row.
|
|
64
|
+
return {
|
|
65
|
+
status: 'skipped',
|
|
66
|
+
message: `${unknown.length}/${ids.length} invariant(s) have no subject outside a live run: ${names}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
status: 'pass',
|
|
71
|
+
message: `${ids.length} invariant(s) hold: ${rows.map(label).join(', ')}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export const invariantsCheck = {
|
|
75
|
+
id: 'runtime.invariants',
|
|
76
|
+
category: 'runtime',
|
|
77
|
+
run: () => describeInvariants(invariants),
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=invariants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariants.js","sourceRoot":"","sources":["../../../src/doctor/checks/invariants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DoctorCheck, DoctorCheckResult, LogSinkCounters } from '@namzu/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Exported separately from the check so all three outcomes can be driven
|
|
4
|
+
* directly, the same arrangement `describeInstalledPackage` uses: a check
|
|
5
|
+
* whose only entry point is `run` can be tested for the state the machine
|
|
6
|
+
* happens to be in and nothing else.
|
|
7
|
+
*/
|
|
8
|
+
export declare function describeLogPipeline(counters: LogSinkCounters | undefined): DoctorCheckResult;
|
|
9
|
+
export declare const loggingPipelineCheck: DoctorCheck;
|
|
10
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkCjF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,iBAAiB,CA6B5F;AAED,eAAO,MAAM,oBAAoB,EAAE,WAOlC,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getLogCounters } from '@namzu/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* What the log pipeline actually did to this process's records.
|
|
4
|
+
*
|
|
5
|
+
* `LogSinkCounters` has counted five things on every record since the sink
|
|
6
|
+
* seam landed — dropped, redacted, attributes shed, values truncated,
|
|
7
|
+
* records shrunk — and until this check nothing read any of them. That is
|
|
8
|
+
* `declared-but-undriven` with an unusually direct tell: the comment above
|
|
9
|
+
* `createLogger` promised "a caller that wants to observe the pipeline — a
|
|
10
|
+
* test, `namzu doctor` later — reads `.counters` off the same reference",
|
|
11
|
+
* and `namzu doctor` did not. This file is that comment coming true.
|
|
12
|
+
*
|
|
13
|
+
* The counters only became readable as part of this change. They lived on
|
|
14
|
+
* whatever logger `createLogger` happened to build, and `getRootLogger`
|
|
15
|
+
* builds a new one on every call, so each count died with the expression
|
|
16
|
+
* that produced it. `installProcessSink` now owns one set for the process
|
|
17
|
+
* and every logger routed through it adds to those totals.
|
|
18
|
+
*/
|
|
19
|
+
/** Field-by-field, so a new counter cannot be silently left unreported. */
|
|
20
|
+
const FIELDS = [
|
|
21
|
+
'dropped',
|
|
22
|
+
'redacted',
|
|
23
|
+
'attributesDropped',
|
|
24
|
+
'valuesTruncated',
|
|
25
|
+
'recordsTruncated',
|
|
26
|
+
];
|
|
27
|
+
function summarise(counters) {
|
|
28
|
+
return FIELDS.map((f) => `${f}=${counters[f]}`).join(' ');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Exported separately from the check so all three outcomes can be driven
|
|
32
|
+
* directly, the same arrangement `describeInstalledPackage` uses: a check
|
|
33
|
+
* whose only entry point is `run` can be tested for the state the machine
|
|
34
|
+
* happens to be in and nothing else.
|
|
35
|
+
*/
|
|
36
|
+
export function describeLogPipeline(counters) {
|
|
37
|
+
if (counters === undefined) {
|
|
38
|
+
// NOT `pass`, and not five zeros dressed up as one. Nothing measured
|
|
39
|
+
// these records, so "nothing was dropped" is a claim this check has no
|
|
40
|
+
// standing to make — and it is the claim a reader would take from a
|
|
41
|
+
// green row. The CLI installs a sink during boot, so reaching here at
|
|
42
|
+
// all means that boot step did not run.
|
|
43
|
+
return {
|
|
44
|
+
status: 'inconclusive',
|
|
45
|
+
message: 'no log sink installed — records reach stderr through the legacy path, where nothing is counted, redacted or size-capped',
|
|
46
|
+
remediation: 'A host owns its log destination: call installProcessSink(sink, level) during boot. The CLI does this itself, so seeing this from `namzu doctor` means that step did not run.',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (counters.dropped > 0) {
|
|
50
|
+
return {
|
|
51
|
+
status: 'fail',
|
|
52
|
+
message: `${counters.dropped} log record(s) never reached the sink — ${summarise(counters)}`,
|
|
53
|
+
remediation: 'A record is counted dropped when the installed sink threw, or when the sink is NOOP_SINK. Check the sink passed to installProcessSink: a sink that throws is caught so one bad record cannot fail a run, which is exactly why the count is the only place this shows up.',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
status: 'pass',
|
|
58
|
+
message: `log pipeline live, nothing dropped — ${summarise(counters)}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export const loggingPipelineCheck = {
|
|
62
|
+
id: 'logging.pipeline',
|
|
63
|
+
// `DoctorCategory` has no `logging` member, and widening that union is an
|
|
64
|
+
// SDK type change this check does not need — the same call the optional
|
|
65
|
+
// package probes made for `files` and `computer-use`.
|
|
66
|
+
category: 'custom',
|
|
67
|
+
run: () => Promise.resolve(describeLogPipeline(getLogCounters())),
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../../../src/doctor/checks/logging.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;;;;;GAgBG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,GAAuC;IAClD,SAAS;IACT,UAAU;IACV,mBAAmB;IACnB,iBAAiB;IACjB,kBAAkB;CAClB,CAAA;AAED,SAAS,SAAS,CAAC,QAAyB;IAC3C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAqC;IACxE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,qEAAqE;QACrE,uEAAuE;QACvE,oEAAoE;QACpE,sEAAsE;QACtE,wCAAwC;QACxC,OAAO;YACN,MAAM,EAAE,cAAc;YACtB,OAAO,EACN,yHAAyH;YAC1H,WAAW,EACV,8KAA8K;SAC/K,CAAA;IACF,CAAC;IAED,IAAI,QAAQ,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,QAAQ,CAAC,OAAO,2CAA2C,SAAS,CAAC,QAAQ,CAAC,EAAE;YAC5F,WAAW,EACV,0QAA0Q;SAC3Q,CAAA;IACF,CAAC;IAED,OAAO;QACN,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,wCAAwC,SAAS,CAAC,QAAQ,CAAC,EAAE;KACtE,CAAA;AACF,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAChD,EAAE,EAAE,kBAAkB;IACtB,0EAA0E;IAC1E,wEAAwE;IACxE,sDAAsD;IACtD,QAAQ,EAAE,QAAQ;IAClB,GAAG,EAAE,GAA+B,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,cAAc,EAAE,CAAC,CAAC;CAC7F,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DoctorCheck, DoctorCheckResult } from '@namzu/sdk';
|
|
2
|
+
import type { NamzuCliConfig } from '../../config/schema.js';
|
|
3
|
+
import { type TelemetryLoader } from '../../integrations/telemetry/session-export.js';
|
|
4
|
+
/**
|
|
5
|
+
* Is this machine sending conversation content anywhere?
|
|
6
|
+
*
|
|
7
|
+
* A doctor row rather than only a boot line, because the two answer for
|
|
8
|
+
* different people at different moments: the boot narrative tells whoever
|
|
9
|
+
* started the run, and `namzu doctor` is what somebody runs when they are
|
|
10
|
+
* asked "does this tool send our transcripts off the box" and need an answer
|
|
11
|
+
* they can paste.
|
|
12
|
+
*
|
|
13
|
+
* The row is a `pass` in both directions on purpose. Export being ON is not
|
|
14
|
+
* a fault — it is a configuration somebody chose — so reporting it as a
|
|
15
|
+
* warning would train a reader to ignore the one row that describes their
|
|
16
|
+
* data leaving. What the row owes them is the destination and the redactor
|
|
17
|
+
* count, which is exactly what `describeSessionExport` already builds.
|
|
18
|
+
*/
|
|
19
|
+
export declare function describeSessionExportStatus(config: NamzuCliConfig, loader?: TelemetryLoader): Promise<DoctorCheckResult>;
|
|
20
|
+
export declare function sessionExportCheck(config: NamzuCliConfig, loader?: TelemetryLoader): DoctorCheck;
|
|
21
|
+
//# sourceMappingURL=session-export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-export.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/session-export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EACN,KAAK,eAAe,EAEpB,MAAM,gDAAgD,CAAA;AAEvD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,2BAA2B,CAChD,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAyC5B;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,WAAW,CAMhG"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describeSessionExportOff, } from '../../integrations/telemetry/session-export.js';
|
|
2
|
+
/**
|
|
3
|
+
* Is this machine sending conversation content anywhere?
|
|
4
|
+
*
|
|
5
|
+
* A doctor row rather than only a boot line, because the two answer for
|
|
6
|
+
* different people at different moments: the boot narrative tells whoever
|
|
7
|
+
* started the run, and `namzu doctor` is what somebody runs when they are
|
|
8
|
+
* asked "does this tool send our transcripts off the box" and need an answer
|
|
9
|
+
* they can paste.
|
|
10
|
+
*
|
|
11
|
+
* The row is a `pass` in both directions on purpose. Export being ON is not
|
|
12
|
+
* a fault — it is a configuration somebody chose — so reporting it as a
|
|
13
|
+
* warning would train a reader to ignore the one row that describes their
|
|
14
|
+
* data leaving. What the row owes them is the destination and the redactor
|
|
15
|
+
* count, which is exactly what `describeSessionExport` already builds.
|
|
16
|
+
*/
|
|
17
|
+
export async function describeSessionExportStatus(config, loader) {
|
|
18
|
+
const configured = config.telemetry?.sessionExport;
|
|
19
|
+
if (!configured) {
|
|
20
|
+
const off = await describeSessionExportOff(loader);
|
|
21
|
+
return {
|
|
22
|
+
status: 'pass',
|
|
23
|
+
message: off ?? 'Session export is off: telemetry.sessionExport is not configured.',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// The disclosure is built from the config, not from a live listener: a
|
|
27
|
+
// doctor run must be able to answer this without starting a session, and
|
|
28
|
+
// starting one to find out what it would export would be the check
|
|
29
|
+
// causing the thing it reports on.
|
|
30
|
+
const off = await describeSessionExportOff(loader);
|
|
31
|
+
if (off === null) {
|
|
32
|
+
// Configured, but the package that would do it is not installed. The
|
|
33
|
+
// run itself refuses in this state (`attachSessionExport`), and saying
|
|
34
|
+
// so here is what stops an operator discovering it at run time.
|
|
35
|
+
return {
|
|
36
|
+
status: 'fail',
|
|
37
|
+
message: `telemetry.sessionExport is configured (destination: ${configured.destination}) but "@namzu/telemetry" is not installed, so nothing would be recorded.`,
|
|
38
|
+
remediation: 'Install @namzu/telemetry, or remove telemetry.sessionExport from your config. namzu refuses to start a session in this state rather than run one whose export silently does not exist.',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const redactorCount = configured.redactors?.length ?? 1;
|
|
42
|
+
const typePhrase = configured.eventTypes
|
|
43
|
+
? `${configured.eventTypes.length} event type(s)`
|
|
44
|
+
: 'every run event';
|
|
45
|
+
return {
|
|
46
|
+
status: 'pass',
|
|
47
|
+
message: `Session export is ON: ${typePhrase} is written to ${configured.destination}, with ${redactorCount} redactor(s) installed.`,
|
|
48
|
+
...(redactorCount === 0
|
|
49
|
+
? {
|
|
50
|
+
remediation: '`redactors: []` turns redaction off entirely — credential-shaped values in tool results will be written verbatim. Remove the key to install the shipped `secrets` redactor.',
|
|
51
|
+
}
|
|
52
|
+
: {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function sessionExportCheck(config, loader) {
|
|
56
|
+
return {
|
|
57
|
+
id: 'telemetry.session-export',
|
|
58
|
+
category: 'telemetry',
|
|
59
|
+
run: () => describeSessionExportStatus(config, loader),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=session-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-export.js","sourceRoot":"","sources":["../../../src/doctor/checks/session-export.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,wBAAwB,GACxB,MAAM,gDAAgD,CAAA;AAEvD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAChD,MAAsB,EACtB,MAAwB;IAExB,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,EAAE,aAAa,CAAA;IAClD,IAAI,CAAC,UAAU,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,MAAM,wBAAwB,CAAC,MAAM,CAAC,CAAA;QAClD,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,IAAI,mEAAmE;SACnF,CAAA;IACF,CAAC;IAED,uEAAuE;IACvE,yEAAyE;IACzE,mEAAmE;IACnE,mCAAmC;IACnC,MAAM,GAAG,GAAG,MAAM,wBAAwB,CAAC,MAAM,CAAC,CAAA;IAClD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAClB,qEAAqE;QACrE,uEAAuE;QACvE,gEAAgE;QAChE,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uDAAuD,UAAU,CAAC,WAAW,0EAA0E;YAChK,WAAW,EACV,wLAAwL;SACzL,CAAA;IACF,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAA;IACvD,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU;QACvC,CAAC,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,gBAAgB;QACjD,CAAC,CAAC,iBAAiB,CAAA;IACpB,OAAO;QACN,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,yBAAyB,UAAU,kBAAkB,UAAU,CAAC,WAAW,UAAU,aAAa,yBAAyB;QACpI,GAAG,CAAC,aAAa,KAAK,CAAC;YACtB,CAAC,CAAC;gBACA,WAAW,EACV,6KAA6K;aAC9K;YACF,CAAC,CAAC,EAAE,CAAC;KACN,CAAA;AACF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,MAAwB;IAClF,OAAO;QACN,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,WAAW;QACrB,GAAG,EAAE,GAA+B,EAAE,CAAC,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC;KAClF,CAAA;AACF,CAAC"}
|
|
@@ -8,38 +8,25 @@ import type { DoctorCheck, DoctorCheckResult } from '@namzu/sdk';
|
|
|
8
8
|
* deferred to a follow-up check that the consumer can register
|
|
9
9
|
* themselves once their telemetry config is known.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* Splitting the question is what makes each answer honest, and a code check on
|
|
20
|
-
* the thrown error is not enough to do it: a transitive dependency that cannot
|
|
21
|
-
* resolve raises `ERR_MODULE_NOT_FOUND` exactly as a missing `@namzu/telemetry`
|
|
22
|
-
* does, so matching on the code would report a broken installation as no
|
|
23
|
-
* installation. `resolve` asks only about THIS specifier, so it separates the
|
|
24
|
-
* two by construction rather than by reading a message that may be reworded.
|
|
25
|
-
*
|
|
26
|
-
* - cannot resolve → the package is not here → `skipped`
|
|
27
|
-
* - resolves, import throws → it is here and unusable → `fail`
|
|
11
|
+
* The three-state resolve-then-import probe this used to do inline now
|
|
12
|
+
* lives at `context/capabilities.ts` as `probeOptionalPackage` (NZ-BOOT-02),
|
|
13
|
+
* extracted so the boot path can report the SAME answer about
|
|
14
|
+
* `@namzu/sandbox`, `@namzu/files` and `@namzu/computer-use` that this file
|
|
15
|
+
* has always given about telemetry, instead of inventing its own. What
|
|
16
|
+
* stays here is the one thing the doctor still owns: turning that answer
|
|
17
|
+
* into the prose and remediation a `DoctorCheckResult` carries.
|
|
28
18
|
*/
|
|
29
19
|
/**
|
|
30
|
-
*
|
|
20
|
+
* `specifier`'s `CapabilityProbe`, read as a `DoctorCheckResult`.
|
|
31
21
|
*
|
|
32
22
|
* Exported separately from the `DoctorCheck` so all three outcomes can be
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* code path serves a package name and a file, which is what lets the
|
|
41
|
-
* present-but-broken case be reached at all. Resolution has already applied the
|
|
42
|
-
* package's export map by that point.
|
|
23
|
+
* asserted directly — there is no way to uninstall or break a real optional
|
|
24
|
+
* package from inside a test run, so `capabilities.test.ts` drives
|
|
25
|
+
* `probeOptionalPackage` with fixtures and this function's own tests only
|
|
26
|
+
* need to check the mapping. The check itself is still exercised through
|
|
27
|
+
* `telemetryInstalledCheck.run`, because a helper proven in isolation says
|
|
28
|
+
* nothing about whether the check reaches it. Same arrangement, and the
|
|
29
|
+
* same reason, as `describeProviderChain`.
|
|
43
30
|
*/
|
|
44
31
|
export declare function describeInstalledPackage(specifier: string): Promise<DoctorCheckResult>;
|
|
45
32
|
export declare const telemetryInstalledCheck: DoctorCheck;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/telemetry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAMhE;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;;;;;;;GAWG;AACH,wBAAsB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoB5F;AAED,eAAO,MAAM,uBAAuB,EAAE,WAIrC,CAAA"}
|