@adhdev/daemon-core 0.9.82-rc.20 → 0.9.82-rc.201
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/boot/daemon-lifecycle.d.ts +1 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +23 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +26 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +7 -1
- package/dist/git/git-types.d.ts +2 -0
- package/dist/index.d.ts +39 -10
- package/dist/index.js +25488 -8333
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29124 -12045
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +30 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +126 -0
- package/dist/mesh/mesh-events.d.ts +164 -6
- package/dist/mesh/mesh-fast-forward.d.ts +41 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-runtime-store.d.ts +149 -0
- package/dist/mesh/mesh-work-queue.d.ts +55 -6
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +39 -3
- package/dist/providers/contracts.d.ts +130 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +12 -0
- package/dist/providers/provider-instance.d.ts +13 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +60 -0
- package/dist/providers/spec/cli-adapter.d.ts +101 -0
- package/dist/providers/spec/driver.d.ts +195 -0
- package/dist/providers/spec/evaluator.d.ts +55 -0
- package/dist/providers/spec/loader.d.ts +14 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +599 -0
- package/dist/providers/spec/types.d.ts +282 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +108 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types-extra.d.ts +1 -1
- package/dist/shared-types.d.ts +42 -1
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -2
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +19 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1138 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1787 -60
- package/src/commands/cli-manager.ts +283 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +331 -122
- package/src/commands/router.ts +3628 -515
- package/src/config/chat-history.ts +95 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +280 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +37 -7
- package/src/git/git-status.ts +35 -6
- package/src/git/git-types.ts +2 -0
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +119 -9
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +204 -30
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-delivery-policy.ts +298 -0
- package/src/mesh/mesh-events.ts +1341 -109
- package/src/mesh/mesh-fast-forward.ts +438 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-runtime-store.ts +769 -0
- package/src/mesh/mesh-work-queue.ts +267 -159
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +806 -64
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +582 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +176 -0
- package/src/providers/spec/cli-adapter.ts +573 -0
- package/src/providers/spec/driver.ts +733 -0
- package/src/providers/spec/evaluator.ts +373 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +939 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +559 -0
- package/src/providers/spec/schema.json +237 -0
- package/src/providers/spec/types.ts +301 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +425 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +118 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types-extra.ts +1 -1
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest validators — runtime JSON Schema check for v1 provider manifests.
|
|
3
|
+
*
|
|
4
|
+
* The schemas live under `schemas/{category}/provider.schema.json` and are
|
|
5
|
+
* the authoritative contract for what a v1 manifest may contain. Daemon
|
|
6
|
+
* code that loads a manifest at install or boot time runs it through
|
|
7
|
+
* `validateCliProviderManifest()` first; failures are reported back to
|
|
8
|
+
* the caller with structured paths so manifest authors can locate the
|
|
9
|
+
* offending field without guessing.
|
|
10
|
+
*
|
|
11
|
+
* Validation lives in the SDK layer (not in provider-loader) so dashboards,
|
|
12
|
+
* registry workers, and the provider publish flow can all reuse the
|
|
13
|
+
* same code path and produce identical error messages.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import Ajv2020, { type ErrorObject, type ValidateFunction } from 'ajv/dist/2020.js';
|
|
17
|
+
import addFormats from 'ajv-formats';
|
|
18
|
+
import cliProviderSchema from '../schemas/cli/provider.schema.json';
|
|
19
|
+
|
|
20
|
+
export interface ManifestValidationIssue {
|
|
21
|
+
/** JSON Pointer-style path into the manifest. Empty string for the root. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Schema-keyword that failed. */
|
|
24
|
+
keyword: string;
|
|
25
|
+
/** Human-readable explanation. */
|
|
26
|
+
message: string;
|
|
27
|
+
/** Allowed values when the failure is an `enum` / `const` / `additionalProperties`. */
|
|
28
|
+
allowed?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ManifestValidationResult {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
issues: ManifestValidationIssue[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let _ajv: Ajv2020 | null = null;
|
|
37
|
+
let _cliValidator: ValidateFunction | null = null;
|
|
38
|
+
|
|
39
|
+
function getAjv(): Ajv2020 {
|
|
40
|
+
if (_ajv) return _ajv;
|
|
41
|
+
// strict=false because the schema authors freely use schema-level
|
|
42
|
+
// `description`/`examples` fields that ajv would otherwise warn about.
|
|
43
|
+
// allErrors=true so a single validation run reports every problem at
|
|
44
|
+
// once instead of bailing on the first — that's what makes the
|
|
45
|
+
// structured-error output usable for manifest authors.
|
|
46
|
+
_ajv = new Ajv2020({
|
|
47
|
+
strict: false,
|
|
48
|
+
allErrors: true,
|
|
49
|
+
allowUnionTypes: true,
|
|
50
|
+
});
|
|
51
|
+
addFormats(_ajv);
|
|
52
|
+
return _ajv;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getCliValidator(): ValidateFunction {
|
|
56
|
+
if (_cliValidator) return _cliValidator;
|
|
57
|
+
_cliValidator = getAjv().compile(cliProviderSchema as unknown as object);
|
|
58
|
+
return _cliValidator;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatIssue(err: ErrorObject): ManifestValidationIssue {
|
|
62
|
+
const path = err.instancePath || '';
|
|
63
|
+
const params = err.params as Record<string, unknown>;
|
|
64
|
+
let message = err.message || 'validation failed';
|
|
65
|
+
let allowed: unknown;
|
|
66
|
+
|
|
67
|
+
if (err.keyword === 'additionalProperties') {
|
|
68
|
+
const extra = params.additionalProperty;
|
|
69
|
+
message = `unexpected property "${extra}"`;
|
|
70
|
+
} else if (err.keyword === 'required') {
|
|
71
|
+
message = `missing required property "${params.missingProperty}"`;
|
|
72
|
+
} else if (err.keyword === 'enum') {
|
|
73
|
+
allowed = params.allowedValues;
|
|
74
|
+
message = `must be one of ${JSON.stringify(allowed)}`;
|
|
75
|
+
} else if (err.keyword === 'const') {
|
|
76
|
+
allowed = params.allowedValue;
|
|
77
|
+
message = `must equal ${JSON.stringify(allowed)}`;
|
|
78
|
+
} else if (err.keyword === 'type') {
|
|
79
|
+
message = `must be ${params.type}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { path, keyword: err.keyword, message, ...(allowed !== undefined ? { allowed } : {}) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate a CLI provider.v1.json manifest. Returns `{ ok: true, issues: [] }`
|
|
87
|
+
* on success and `{ ok: false, issues: [...] }` on failure with each issue
|
|
88
|
+
* pointing at the offending field. Never throws — manifest parse errors
|
|
89
|
+
* should be caught by the caller before this is called.
|
|
90
|
+
*/
|
|
91
|
+
export function validateCliProviderManifest(manifest: unknown): ManifestValidationResult {
|
|
92
|
+
const validator = getCliValidator();
|
|
93
|
+
const ok = validator(manifest) as boolean;
|
|
94
|
+
if (ok) return { ok: true, issues: [] };
|
|
95
|
+
const issues = (validator.errors || []).map(formatIssue);
|
|
96
|
+
return { ok: false, issues };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Render a list of validation issues as a single multi-line string,
|
|
101
|
+
* suitable for log lines or daemon command error fields.
|
|
102
|
+
*/
|
|
103
|
+
export function formatManifestValidationIssues(issues: ManifestValidationIssue[]): string {
|
|
104
|
+
return issues
|
|
105
|
+
.map((issue) => {
|
|
106
|
+
const where = issue.path || '<root>';
|
|
107
|
+
return ` - ${where}: ${issue.message} (${issue.keyword})`;
|
|
108
|
+
})
|
|
109
|
+
.join('\n');
|
|
110
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static taint analyzer for extended-tier override JS.
|
|
3
|
+
*
|
|
4
|
+
* Goal: classify the override JS shipped with an extended-tier provider into
|
|
5
|
+
* one of three risk tiers, so the dashboard + daemon trust prompt can
|
|
6
|
+
* surface accurate language to the operator instead of a single generic
|
|
7
|
+
* "this provider contains JS" warning.
|
|
8
|
+
*
|
|
9
|
+
* - clean : no flagged APIs at all. The override looks like pure string
|
|
10
|
+
* manipulation. Still extended-tier, but the warning copy is
|
|
11
|
+
* "this provider contains JS; nothing dangerous was detected".
|
|
12
|
+
* - elevated : touches process / dynamic codegen / dynamic require / eval
|
|
13
|
+
* / Function constructor / child_process. The warning copy is
|
|
14
|
+
* explicit ("this provider can run shell commands / dynamically
|
|
15
|
+
* load code").
|
|
16
|
+
* - hostile : combines elevated APIs with network egress, AND uses obvious
|
|
17
|
+
* obfuscation (base64-decoded strings fed into Function / eval).
|
|
18
|
+
* The publish endpoint should reject these.
|
|
19
|
+
*
|
|
20
|
+
* Implementation note: this is a *static* analyzer — string + light AST scan
|
|
21
|
+
* via Node's built-in parser. It is intentionally fail-loud: anything that
|
|
22
|
+
* looks suspicious is flagged. False positives are fine; this exists to gate
|
|
23
|
+
* the trust prompt, not to be a real sandbox. The real sandbox lives in
|
|
24
|
+
* Phase 5 (isolated-vm).
|
|
25
|
+
*
|
|
26
|
+
* Used by:
|
|
27
|
+
* - `adhdev provider validate` (CLI tier classification)
|
|
28
|
+
* - registry publish endpoint static-check
|
|
29
|
+
* - daemon at provider-load time, to size the warning
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
33
|
+
import { resolve, dirname, join } from 'node:path';
|
|
34
|
+
|
|
35
|
+
// ─── Findings ──────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export type TaintLevel = 'clean' | 'elevated' | 'hostile';
|
|
38
|
+
|
|
39
|
+
export interface TaintFinding {
|
|
40
|
+
/** What was found, e.g. "child_process.exec". */
|
|
41
|
+
api: string;
|
|
42
|
+
/** Coarse category. */
|
|
43
|
+
category: TaintCategory;
|
|
44
|
+
/** File the finding came from. */
|
|
45
|
+
file: string;
|
|
46
|
+
/** Line number (1-indexed) of the first character of the matched token. */
|
|
47
|
+
line: number;
|
|
48
|
+
/** The surrounding source excerpt, trimmed. */
|
|
49
|
+
excerpt: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type TaintCategory =
|
|
53
|
+
| 'process-control' // process.exit, process.kill
|
|
54
|
+
| 'shell-exec' // child_process.{exec,execSync,spawn,spawnSync,fork}
|
|
55
|
+
| 'fs-write' // fs.writeFile / writeFileSync outside override dir
|
|
56
|
+
| 'fs-read' // fs.readFile / readFileSync
|
|
57
|
+
| 'network' // http(s).request, fetch, net.connect, dgram
|
|
58
|
+
| 'dynamic-codegen' // eval, new Function(), Function('...')
|
|
59
|
+
| 'dynamic-require' // require(variable), import(variable)
|
|
60
|
+
| 'obfuscation' // Buffer.from(...,'base64').toString + (eval|Function)
|
|
61
|
+
| 'global-mutation'; // mutating Object.prototype, process.env
|
|
62
|
+
|
|
63
|
+
export interface TaintResult {
|
|
64
|
+
level: TaintLevel;
|
|
65
|
+
findings: TaintFinding[];
|
|
66
|
+
files: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Patterns ──────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
interface PatternRule {
|
|
72
|
+
category: TaintCategory;
|
|
73
|
+
/** Display label. */
|
|
74
|
+
label: string;
|
|
75
|
+
/** Regex applied per-line. */
|
|
76
|
+
pattern: RegExp;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const PATTERNS: PatternRule[] = [
|
|
80
|
+
{
|
|
81
|
+
category: 'shell-exec',
|
|
82
|
+
label: 'child_process.exec',
|
|
83
|
+
pattern: /\bchild_process\b[\s\S]{0,40}?\b(exec|execSync|spawn|spawnSync|fork|execFile)\b/,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
category: 'shell-exec',
|
|
87
|
+
label: 'require("child_process")',
|
|
88
|
+
pattern: /\brequire\s*\(\s*['"]child_process['"]\s*\)/,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
category: 'shell-exec',
|
|
92
|
+
label: 'import child_process',
|
|
93
|
+
pattern: /\bfrom\s+['"](?:node:)?child_process['"]/,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
category: 'process-control',
|
|
97
|
+
label: 'process.exit / kill',
|
|
98
|
+
pattern: /\bprocess\.(exit|kill|abort)\s*\(/,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
category: 'global-mutation',
|
|
102
|
+
label: 'process.env mutation',
|
|
103
|
+
pattern: /\bprocess\.env\.[A-Z_][A-Z0-9_]*\s*=/,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
category: 'global-mutation',
|
|
107
|
+
label: 'prototype pollution',
|
|
108
|
+
pattern: /\bObject\.prototype\b.*=/,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
category: 'dynamic-codegen',
|
|
112
|
+
label: 'eval()',
|
|
113
|
+
pattern: /(?<![A-Za-z0-9_$])eval\s*\(/,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
category: 'dynamic-codegen',
|
|
117
|
+
label: 'new Function()',
|
|
118
|
+
pattern: /\bnew\s+Function\s*\(/,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
category: 'dynamic-require',
|
|
122
|
+
label: 'require(variable)',
|
|
123
|
+
pattern: /\brequire\s*\(\s*(?!['"`])/,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
category: 'dynamic-require',
|
|
127
|
+
label: 'import(variable)',
|
|
128
|
+
pattern: /(?<![.\w])import\s*\(\s*(?!['"`])/,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
category: 'network',
|
|
132
|
+
label: 'fetch()',
|
|
133
|
+
pattern: /(?<![A-Za-z0-9_$])fetch\s*\(/,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
category: 'network',
|
|
137
|
+
label: 'http(s).request',
|
|
138
|
+
pattern: /\b(https?)\.(request|get)\s*\(/,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
category: 'network',
|
|
142
|
+
label: 'net.connect / Socket',
|
|
143
|
+
pattern: /\b(net\.connect|tls\.connect|new\s+net\.Socket)\b/,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
category: 'network',
|
|
147
|
+
label: 'dgram',
|
|
148
|
+
pattern: /\brequire\s*\(\s*['"](?:node:)?dgram['"]/,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
category: 'fs-write',
|
|
152
|
+
label: 'fs.writeFile / writeFileSync',
|
|
153
|
+
pattern: /\bfs\.(writeFile|writeFileSync|appendFile|appendFileSync|unlink|unlinkSync|rm|rmSync)\b/,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
category: 'fs-read',
|
|
157
|
+
label: 'fs.readFile / readFileSync',
|
|
158
|
+
pattern: /\bfs\.(readFile|readFileSync|readdir|readdirSync|stat|statSync)\b/,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
// Obfuscation heuristic: base64 / hex decode feeding into eval or Function.
|
|
163
|
+
const OBFUSCATION_PATTERN =
|
|
164
|
+
/Buffer\.from\s*\([^)]*['"](?:base64|hex)['"]\s*\)\s*\.toString\s*\([^)]*\)[\s\S]{0,80}?(eval|Function)\s*\(/;
|
|
165
|
+
|
|
166
|
+
// ─── File walker — picks up everything reachable from overrides ────────
|
|
167
|
+
|
|
168
|
+
function listOverrideJsFiles(manifestPath: string): string[] {
|
|
169
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as { overrides?: Record<string, unknown> };
|
|
170
|
+
if (!manifest.overrides) return [];
|
|
171
|
+
const base = dirname(manifestPath);
|
|
172
|
+
const out = new Set<string>();
|
|
173
|
+
for (const value of Object.values(manifest.overrides)) {
|
|
174
|
+
let rel: string | null = null;
|
|
175
|
+
if (typeof value === 'string') rel = value;
|
|
176
|
+
else if (value && typeof value === 'object' && 'path' in value && typeof (value as { path: unknown }).path === 'string') {
|
|
177
|
+
rel = (value as { path: string }).path;
|
|
178
|
+
}
|
|
179
|
+
if (!rel) continue;
|
|
180
|
+
const abs = resolve(base, rel);
|
|
181
|
+
if (existsSync(abs)) out.add(abs);
|
|
182
|
+
}
|
|
183
|
+
// Best-effort: also scan sibling scripts/ tree, since overrides commonly
|
|
184
|
+
// require() helpers from there.
|
|
185
|
+
for (const candidate of ['scripts']) {
|
|
186
|
+
const dir = join(base, candidate);
|
|
187
|
+
if (existsSync(dir)) {
|
|
188
|
+
try {
|
|
189
|
+
walk(dir, out);
|
|
190
|
+
} catch {
|
|
191
|
+
// Ignore individual unreadable files.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return [...out];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function walk(dir: string, into: Set<string>): void {
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
200
|
+
const { readdirSync, statSync } = require('node:fs') as typeof import('node:fs');
|
|
201
|
+
for (const entry of readdirSync(dir)) {
|
|
202
|
+
const abs = join(dir, entry);
|
|
203
|
+
let st;
|
|
204
|
+
try { st = statSync(abs); } catch { continue; }
|
|
205
|
+
if (st.isDirectory()) walk(abs, into);
|
|
206
|
+
else if (st.isFile() && /\.(?:m|c)?js$/.test(entry)) into.add(abs);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── Scanner ───────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
function scanFile(file: string): TaintFinding[] {
|
|
213
|
+
const source = readFileSync(file, 'utf-8');
|
|
214
|
+
const findings: TaintFinding[] = [];
|
|
215
|
+
// Per-line scan keeps line numbers honest.
|
|
216
|
+
const lines = source.split('\n');
|
|
217
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
218
|
+
const line = lines[i];
|
|
219
|
+
for (const rule of PATTERNS) {
|
|
220
|
+
if (rule.pattern.test(line)) {
|
|
221
|
+
findings.push({
|
|
222
|
+
api: rule.label,
|
|
223
|
+
category: rule.category,
|
|
224
|
+
file,
|
|
225
|
+
line: i + 1,
|
|
226
|
+
excerpt: line.trim().slice(0, 200),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Whole-file scan for obfuscation pattern (may span lines).
|
|
232
|
+
if (OBFUSCATION_PATTERN.test(source)) {
|
|
233
|
+
findings.push({
|
|
234
|
+
api: 'base64/hex → eval|Function',
|
|
235
|
+
category: 'obfuscation',
|
|
236
|
+
file,
|
|
237
|
+
line: 1,
|
|
238
|
+
excerpt: '<obfuscation pattern across multiple lines>',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return findings;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── Classification ────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
const ELEVATED_CATEGORIES = new Set<TaintCategory>([
|
|
247
|
+
'shell-exec',
|
|
248
|
+
'process-control',
|
|
249
|
+
'dynamic-codegen',
|
|
250
|
+
'dynamic-require',
|
|
251
|
+
'fs-write',
|
|
252
|
+
'global-mutation',
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
const HOSTILE_REQUIRES = new Set<TaintCategory>([
|
|
256
|
+
'obfuscation',
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
function classify(findings: TaintFinding[]): TaintLevel {
|
|
260
|
+
if (findings.length === 0) return 'clean';
|
|
261
|
+
const cats = new Set(findings.map((f) => f.category));
|
|
262
|
+
if ([...cats].some((c) => HOSTILE_REQUIRES.has(c))) return 'hostile';
|
|
263
|
+
// Hostile also: elevated + network combined.
|
|
264
|
+
const hasElevated = [...cats].some((c) => ELEVATED_CATEGORIES.has(c));
|
|
265
|
+
const hasNetwork = cats.has('network');
|
|
266
|
+
if (hasElevated && hasNetwork) return 'hostile';
|
|
267
|
+
if (hasElevated || hasNetwork) return 'elevated';
|
|
268
|
+
return 'clean'; // only fs-read on its own is fine.
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ─── Public API ────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
export function analyzeOverrideTaint(manifestPath: string): TaintResult {
|
|
274
|
+
const files = listOverrideJsFiles(manifestPath);
|
|
275
|
+
const findings: TaintFinding[] = [];
|
|
276
|
+
for (const f of files) findings.push(...scanFile(f));
|
|
277
|
+
return {
|
|
278
|
+
level: classify(findings),
|
|
279
|
+
findings,
|
|
280
|
+
files,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Format a result as a single CLI-friendly block. */
|
|
285
|
+
export function formatTaintResult(result: TaintResult): string {
|
|
286
|
+
const lines: string[] = [];
|
|
287
|
+
lines.push(`Tier: extended (override JS present)`);
|
|
288
|
+
lines.push(`Risk level: ${result.level}`);
|
|
289
|
+
lines.push(`Files scanned: ${result.files.length}`);
|
|
290
|
+
if (result.findings.length === 0) {
|
|
291
|
+
lines.push('No flagged APIs detected.');
|
|
292
|
+
return lines.join('\n');
|
|
293
|
+
}
|
|
294
|
+
// Group by category for a tighter report.
|
|
295
|
+
const byCat = new Map<TaintCategory, TaintFinding[]>();
|
|
296
|
+
for (const f of result.findings) {
|
|
297
|
+
const arr = byCat.get(f.category) ?? [];
|
|
298
|
+
arr.push(f);
|
|
299
|
+
byCat.set(f.category, arr);
|
|
300
|
+
}
|
|
301
|
+
for (const [cat, items] of byCat) {
|
|
302
|
+
lines.push(` ${cat}: ${items.length} occurrence(s)`);
|
|
303
|
+
for (const f of items.slice(0, 5)) {
|
|
304
|
+
lines.push(` ${f.file}:${f.line} ${f.api}`);
|
|
305
|
+
}
|
|
306
|
+
if (items.length > 5) lines.push(` …and ${items.length - 5} more`);
|
|
307
|
+
}
|
|
308
|
+
return lines.join('\n');
|
|
309
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin generic terminal adapter.
|
|
3
|
+
*
|
|
4
|
+
* Spawns a PTY, feeds bytes into a headless xterm so a visible screen
|
|
5
|
+
* snapshot is always available, and exposes a small hook surface
|
|
6
|
+
* (init / tick / on_screen_changed / on_exit). Nothing in this module
|
|
7
|
+
* knows about agents, modals, or specs — it's just a terminal host.
|
|
8
|
+
*
|
|
9
|
+
* The Spec Driver is built on top of this adapter and consumes the
|
|
10
|
+
* hooks to drive its declarative state machine.
|
|
11
|
+
*
|
|
12
|
+
* Inbound from driver:
|
|
13
|
+
* send_keys(str) — raw bytes to PTY
|
|
14
|
+
* resize(c, r) — resize the PTY and the xterm
|
|
15
|
+
* kill() — terminate the child
|
|
16
|
+
*
|
|
17
|
+
* Outbound to driver:
|
|
18
|
+
* init({ pid })
|
|
19
|
+
* on_pty_data(chunk) — raw bytes for dashboard's terminal pane
|
|
20
|
+
* on_screen_changed(snapshot) — coalesced visible-screen change
|
|
21
|
+
* tick() — periodic timer (configurable interval)
|
|
22
|
+
* on_exit({ exitCode })
|
|
23
|
+
*/
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// @xterm/headless ships as CJS only but stamps __esModule = true on its
|
|
27
|
+
// export object, so tsup's __toESM helper neither synthesizes a `default`
|
|
28
|
+
// property (it sees the __esModule marker and trusts it) nor preserves
|
|
29
|
+
// the named `Terminal` export through a plain ESM-style
|
|
30
|
+
// `import { Terminal } from '@xterm/headless'`. The first symptom we saw
|
|
31
|
+
// from this was the published `adhdev` CLI exploding with
|
|
32
|
+
// "Named export 'Terminal' not found" inside the mcp ipc bootstrap.
|
|
33
|
+
//
|
|
34
|
+
// Pull Terminal off the namespace import (which tsup compiles into a
|
|
35
|
+
// shim that *does* see the package's own Terminal property) instead.
|
|
36
|
+
// Type-only import keeps the .d.ts surface honest.
|
|
37
|
+
import type { Terminal as TerminalType } from '@xterm/headless';
|
|
38
|
+
import * as xtermHeadlessNs from '@xterm/headless';
|
|
39
|
+
const TerminalCtor: { new (...args: unknown[]): TerminalType } =
|
|
40
|
+
(xtermHeadlessNs as any).Terminal
|
|
41
|
+
?? (xtermHeadlessNs as any).default?.Terminal;
|
|
42
|
+
import { NodePtyTransportFactory, type PtyRuntimeTransport, type PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
|
|
43
|
+
|
|
44
|
+
export interface TerminalAdapterOpts {
|
|
45
|
+
binary: string;
|
|
46
|
+
args?: string[];
|
|
47
|
+
cwd: string;
|
|
48
|
+
env?: Record<string, string>;
|
|
49
|
+
cols?: number;
|
|
50
|
+
rows?: number;
|
|
51
|
+
/** Coalesce screen snapshots: emit on_screen_changed at most this often. */
|
|
52
|
+
screenChangeDebounceMs?: number;
|
|
53
|
+
/** tick() period. 0 disables ticks. */
|
|
54
|
+
tickIntervalMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Optional PTY transport factory. When the daemon supplies a
|
|
57
|
+
* SessionHostPtyTransportFactory (standalone runs the PTY inside the
|
|
58
|
+
* session-host so the runtime/<sid>/snapshot endpoint can serve it),
|
|
59
|
+
* forward it here. Without it the PTY spawns locally inside the
|
|
60
|
+
* daemon process and the dashboard's terminal pane reports
|
|
61
|
+
* "Runtime terminal unavailable: Unknown session".
|
|
62
|
+
*/
|
|
63
|
+
transportFactory?: PtyTransportFactory;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TerminalAdapterHandlers {
|
|
67
|
+
init?(info: { pid: number }): void;
|
|
68
|
+
on_pty_data?(chunk: string): void;
|
|
69
|
+
on_screen_changed?(snapshot: string): void;
|
|
70
|
+
tick?(): void;
|
|
71
|
+
on_exit?(info: { exitCode: number }): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class TerminalAdapter {
|
|
75
|
+
private term: TerminalType;
|
|
76
|
+
private pty: PtyRuntimeTransport | null = null;
|
|
77
|
+
private factory: PtyTransportFactory;
|
|
78
|
+
private cols: number;
|
|
79
|
+
private rows: number;
|
|
80
|
+
private screenDebounceMs: number;
|
|
81
|
+
private tickIntervalMs: number;
|
|
82
|
+
private screenTimer: ReturnType<typeof setTimeout> | null = null;
|
|
83
|
+
private tickTimer: ReturnType<typeof setInterval> | null = null;
|
|
84
|
+
private lastScreen = '';
|
|
85
|
+
|
|
86
|
+
constructor(
|
|
87
|
+
private readonly opts: TerminalAdapterOpts,
|
|
88
|
+
private readonly handlers: TerminalAdapterHandlers,
|
|
89
|
+
) {
|
|
90
|
+
this.cols = opts.cols ?? 100;
|
|
91
|
+
this.rows = opts.rows ?? 30;
|
|
92
|
+
this.screenDebounceMs = opts.screenChangeDebounceMs ?? 80;
|
|
93
|
+
this.tickIntervalMs = opts.tickIntervalMs ?? 0;
|
|
94
|
+
this.factory = opts.transportFactory ?? new NodePtyTransportFactory();
|
|
95
|
+
this.term = new TerminalCtor({ cols: this.cols, rows: this.rows, allowProposedApi: true, scrollback: 1000 });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
start(): void {
|
|
99
|
+
this.pty = this.factory.spawn(this.opts.binary, this.opts.args ?? [], {
|
|
100
|
+
cwd: this.opts.cwd,
|
|
101
|
+
env: { ...process.env, ...(this.opts.env ?? {}) } as Record<string, string>,
|
|
102
|
+
cols: this.cols,
|
|
103
|
+
rows: this.rows,
|
|
104
|
+
});
|
|
105
|
+
this.handlers.init?.({ pid: this.pty.pid });
|
|
106
|
+
this.pty.onData((chunk) => this.onChunk(chunk));
|
|
107
|
+
this.pty.onExit((info) => {
|
|
108
|
+
this.stopTimers();
|
|
109
|
+
this.handlers.on_exit?.({ exitCode: typeof info.exitCode === 'number' ? info.exitCode : 0 });
|
|
110
|
+
this.pty = null;
|
|
111
|
+
});
|
|
112
|
+
if (this.tickIntervalMs > 0) {
|
|
113
|
+
this.tickTimer = setInterval(() => this.handlers.tick?.(), this.tickIntervalMs);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
send_keys(s: string): void {
|
|
118
|
+
this.pty?.write(s);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
resize(cols: number, rows: number): void {
|
|
122
|
+
this.cols = cols; this.rows = rows;
|
|
123
|
+
this.pty?.resize(cols, rows);
|
|
124
|
+
this.term.resize(cols, rows);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
snapshot(): string {
|
|
128
|
+
return this.lastScreen || this.computeScreen();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getCursorPosition(): { row: number; col: number } {
|
|
132
|
+
const buf = this.term.buffer.active;
|
|
133
|
+
return {
|
|
134
|
+
row: Math.max(0, (buf as any).cursorY ?? 0),
|
|
135
|
+
col: Math.max(0, (buf as any).cursorX ?? 0),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
kill(): void {
|
|
140
|
+
this.stopTimers();
|
|
141
|
+
try { this.pty?.kill(); } catch { /* ignore */ }
|
|
142
|
+
this.pty = null;
|
|
143
|
+
this.term.dispose();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private onChunk(chunk: string): void {
|
|
147
|
+
try { this.handlers.on_pty_data?.(chunk); } catch { /* user side */ }
|
|
148
|
+
this.term.write(chunk);
|
|
149
|
+
// Coalesce snapshot emission — rapid bursts shouldn't fire 200x.
|
|
150
|
+
if (this.screenTimer) return;
|
|
151
|
+
this.screenTimer = setTimeout(() => {
|
|
152
|
+
this.screenTimer = null;
|
|
153
|
+
const snap = this.computeScreen();
|
|
154
|
+
if (snap === this.lastScreen) return;
|
|
155
|
+
this.lastScreen = snap;
|
|
156
|
+
try { this.handlers.on_screen_changed?.(snap); } catch { /* user side */ }
|
|
157
|
+
}, this.screenDebounceMs);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private computeScreen(): string {
|
|
161
|
+
const buf = this.term.buffer.active;
|
|
162
|
+
const out: string[] = [];
|
|
163
|
+
for (let y = 0; y < buf.length; y += 1) {
|
|
164
|
+
const line = buf.getLine(y);
|
|
165
|
+
if (!line) continue;
|
|
166
|
+
out.push(line.translateToString(true));
|
|
167
|
+
}
|
|
168
|
+
while (out.length > 0 && out[out.length - 1].trim() === '') out.pop();
|
|
169
|
+
return out.join('\n');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private stopTimers(): void {
|
|
173
|
+
if (this.screenTimer) { clearTimeout(this.screenTimer); this.screenTimer = null; }
|
|
174
|
+
if (this.tickTimer) { clearInterval(this.tickTimer); this.tickTimer = null; }
|
|
175
|
+
}
|
|
176
|
+
}
|