@sapiom/harness 0.0.0 → 0.1.1
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/LICENSE +21 -0
- package/README.md +44 -43
- package/dist/cli/auth.d.ts +26 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +39 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +163 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/consent.d.ts +15 -0
- package/dist/cli/consent.d.ts.map +1 -0
- package/dist/cli/consent.js +81 -0
- package/dist/cli/consent.js.map +1 -0
- package/dist/cli/doctor.d.ts +27 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +74 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/machine-id.d.ts +19 -0
- package/dist/cli/machine-id.d.ts.map +1 -0
- package/dist/cli/machine-id.js +75 -0
- package/dist/cli/machine-id.js.map +1 -0
- package/dist/cli/paths.d.ts +3 -0
- package/dist/cli/paths.d.ts.map +1 -0
- package/dist/cli/paths.js +11 -0
- package/dist/cli/paths.js.map +1 -0
- package/dist/cli/settings.d.ts +26 -0
- package/dist/cli/settings.d.ts.map +1 -0
- package/dist/cli/settings.js +123 -0
- package/dist/cli/settings.js.map +1 -0
- package/dist/core/adapters/claude-code.d.ts +41 -0
- package/dist/core/adapters/claude-code.d.ts.map +1 -0
- package/dist/core/adapters/claude-code.js +238 -0
- package/dist/core/adapters/claude-code.js.map +1 -0
- package/dist/core/adapters/codex.d.ts +59 -0
- package/dist/core/adapters/codex.d.ts.map +1 -0
- package/dist/core/adapters/codex.js +319 -0
- package/dist/core/adapters/codex.js.map +1 -0
- package/dist/core/canvas-body.d.ts +45 -0
- package/dist/core/canvas-body.d.ts.map +1 -0
- package/dist/core/canvas-body.js +94 -0
- package/dist/core/canvas-body.js.map +1 -0
- package/dist/core/canvas-cache.d.ts +23 -0
- package/dist/core/canvas-cache.d.ts.map +1 -0
- package/dist/core/canvas-cache.js +64 -0
- package/dist/core/canvas-cache.js.map +1 -0
- package/dist/core/canvas-enrich.d.ts +106 -0
- package/dist/core/canvas-enrich.d.ts.map +1 -0
- package/dist/core/canvas-enrich.js +178 -0
- package/dist/core/canvas-enrich.js.map +1 -0
- package/dist/core/canvas-enrichment.d.ts +144 -0
- package/dist/core/canvas-enrichment.d.ts.map +1 -0
- package/dist/core/canvas-enrichment.js +192 -0
- package/dist/core/canvas-enrichment.js.map +1 -0
- package/dist/core/canvas-graph.d.ts +71 -0
- package/dist/core/canvas-graph.d.ts.map +1 -0
- package/dist/core/canvas-graph.js +111 -0
- package/dist/core/canvas-graph.js.map +1 -0
- package/dist/core/canvas-index-classify.d.ts +41 -0
- package/dist/core/canvas-index-classify.d.ts.map +1 -0
- package/dist/core/canvas-index-classify.js +63 -0
- package/dist/core/canvas-index-classify.js.map +1 -0
- package/dist/core/canvas-interconnections.d.ts +24 -0
- package/dist/core/canvas-interconnections.d.ts.map +1 -0
- package/dist/core/canvas-interconnections.js +96 -0
- package/dist/core/canvas-interconnections.js.map +1 -0
- package/dist/core/canvas-manifest-check.d.ts +15 -0
- package/dist/core/canvas-manifest-check.d.ts.map +1 -0
- package/dist/core/canvas-manifest-check.js +90 -0
- package/dist/core/canvas-manifest-check.js.map +1 -0
- package/dist/core/canvas-render.d.ts +73 -0
- package/dist/core/canvas-render.d.ts.map +1 -0
- package/dist/core/canvas-render.js +145 -0
- package/dist/core/canvas-render.js.map +1 -0
- package/dist/core/canvas-svg.d.ts +50 -0
- package/dist/core/canvas-svg.d.ts.map +1 -0
- package/dist/core/canvas-svg.js +240 -0
- package/dist/core/canvas-svg.js.map +1 -0
- package/dist/core/canvas-template.d.ts +43 -0
- package/dist/core/canvas-template.d.ts.map +1 -0
- package/dist/core/canvas-template.js +365 -0
- package/dist/core/canvas-template.js.map +1 -0
- package/dist/core/canvas-watcher.d.ts +25 -0
- package/dist/core/canvas-watcher.d.ts.map +1 -0
- package/dist/core/canvas-watcher.js +144 -0
- package/dist/core/canvas-watcher.js.map +1 -0
- package/dist/core/collector/batcher.d.ts +57 -0
- package/dist/core/collector/batcher.d.ts.map +1 -0
- package/dist/core/collector/batcher.js +143 -0
- package/dist/core/collector/batcher.js.map +1 -0
- package/dist/core/collector/codex-tailer.d.ts +85 -0
- package/dist/core/collector/codex-tailer.d.ts.map +1 -0
- package/dist/core/collector/codex-tailer.js +270 -0
- package/dist/core/collector/codex-tailer.js.map +1 -0
- package/dist/core/collector/normalizer.d.ts +33 -0
- package/dist/core/collector/normalizer.d.ts.map +1 -0
- package/dist/core/collector/normalizer.js +94 -0
- package/dist/core/collector/normalizer.js.map +1 -0
- package/dist/core/collector/seq.d.ts +14 -0
- package/dist/core/collector/seq.d.ts.map +1 -0
- package/dist/core/collector/seq.js +20 -0
- package/dist/core/collector/seq.js.map +1 -0
- package/dist/core/collector/store.d.ts +16 -0
- package/dist/core/collector/store.d.ts.map +1 -0
- package/dist/core/collector/store.js +30 -0
- package/dist/core/collector/store.js.map +1 -0
- package/dist/core/collector/transcript.d.ts +38 -0
- package/dist/core/collector/transcript.d.ts.map +1 -0
- package/dist/core/collector/transcript.js +110 -0
- package/dist/core/collector/transcript.js.map +1 -0
- package/dist/core/event-bus.d.ts +9 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +25 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/example-seed.d.ts +34 -0
- package/dist/core/example-seed.d.ts.map +1 -0
- package/dist/core/example-seed.js +277 -0
- package/dist/core/example-seed.js.map +1 -0
- package/dist/core/inject/claude-settings.d.ts +34 -0
- package/dist/core/inject/claude-settings.d.ts.map +1 -0
- package/dist/core/inject/claude-settings.js +152 -0
- package/dist/core/inject/claude-settings.js.map +1 -0
- package/dist/core/inject/mcp-config.d.ts +26 -0
- package/dist/core/inject/mcp-config.d.ts.map +1 -0
- package/dist/core/inject/mcp-config.js +39 -0
- package/dist/core/inject/mcp-config.js.map +1 -0
- package/dist/core/inject/retention.d.ts +66 -0
- package/dist/core/inject/retention.d.ts.map +1 -0
- package/dist/core/inject/retention.js +119 -0
- package/dist/core/inject/retention.js.map +1 -0
- package/dist/core/inject/system-prompt.d.ts +11 -0
- package/dist/core/inject/system-prompt.d.ts.map +1 -0
- package/dist/core/inject/system-prompt.js +22 -0
- package/dist/core/inject/system-prompt.js.map +1 -0
- package/dist/core/macro-runner.d.ts +36 -0
- package/dist/core/macro-runner.d.ts.map +1 -0
- package/dist/core/macro-runner.js +66 -0
- package/dist/core/macro-runner.js.map +1 -0
- package/dist/core/macros.d.ts +10 -0
- package/dist/core/macros.d.ts.map +1 -0
- package/dist/core/macros.js +59 -0
- package/dist/core/macros.js.map +1 -0
- package/dist/core/path-safety.d.ts +33 -0
- package/dist/core/path-safety.d.ts.map +1 -0
- package/dist/core/path-safety.js +63 -0
- package/dist/core/path-safety.js.map +1 -0
- package/dist/core/paths.d.ts +27 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +43 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/port-detector.d.ts +69 -0
- package/dist/core/port-detector.d.ts.map +1 -0
- package/dist/core/port-detector.js +121 -0
- package/dist/core/port-detector.js.map +1 -0
- package/dist/core/session-manager.d.ts +275 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +729 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/task-manager.d.ts +138 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +286 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/task-stream.d.ts +32 -0
- package/dist/core/task-stream.d.ts.map +1 -0
- package/dist/core/task-stream.js +87 -0
- package/dist/core/task-stream.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +36 -0
- package/dist/core/workflow-registry.d.ts.map +1 -0
- package/dist/core/workflow-registry.js +220 -0
- package/dist/core/workflow-registry.js.map +1 -0
- package/dist/core/workspace-context.d.ts +43 -0
- package/dist/core/workspace-context.d.ts.map +1 -0
- package/dist/core/workspace-context.js +108 -0
- package/dist/core/workspace-context.js.map +1 -0
- package/dist/core/workspace-watcher.d.ts +26 -0
- package/dist/core/workspace-watcher.d.ts.map +1 -0
- package/dist/core/workspace-watcher.js +174 -0
- package/dist/core/workspace-watcher.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/canvas-guidelines.d.ts +20 -0
- package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
- package/dist/profiles/canvas-guidelines.js +48 -0
- package/dist/profiles/canvas-guidelines.js.map +1 -0
- package/dist/profiles/default.d.ts +10 -0
- package/dist/profiles/default.d.ts.map +1 -0
- package/dist/profiles/default.js +70 -0
- package/dist/profiles/default.js.map +1 -0
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/canvas-render.d.ts +18 -0
- package/dist/server/canvas-render.d.ts.map +1 -0
- package/dist/server/canvas-render.js +24 -0
- package/dist/server/canvas-render.js.map +1 -0
- package/dist/server/canvas.d.ts +12 -0
- package/dist/server/canvas.d.ts.map +1 -0
- package/dist/server/canvas.js +174 -0
- package/dist/server/canvas.js.map +1 -0
- package/dist/server/events-ws.d.ts +11 -0
- package/dist/server/events-ws.d.ts.map +1 -0
- package/dist/server/events-ws.js +24 -0
- package/dist/server/events-ws.js.map +1 -0
- package/dist/server/fs.d.ts +4 -0
- package/dist/server/fs.d.ts.map +1 -0
- package/dist/server/fs.js +88 -0
- package/dist/server/fs.js.map +1 -0
- package/dist/server/index.d.ts +83 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/ingest.d.ts +74 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +74 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/server/macros.d.ts +32 -0
- package/dist/server/macros.d.ts.map +1 -0
- package/dist/server/macros.js +76 -0
- package/dist/server/macros.js.map +1 -0
- package/dist/server/rest.d.ts +75 -0
- package/dist/server/rest.d.ts.map +1 -0
- package/dist/server/rest.js +235 -0
- package/dist/server/rest.js.map +1 -0
- package/dist/server/static.d.ts +8 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +36 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/terminal-ws.d.ts +9 -0
- package/dist/server/terminal-ws.d.ts.map +1 -0
- package/dist/server/terminal-ws.js +59 -0
- package/dist/server/terminal-ws.js.map +1 -0
- package/dist/server/ws-router.d.ts +16 -0
- package/dist/server/ws-router.d.ts.map +1 -0
- package/dist/server/ws-router.js +26 -0
- package/dist/server/ws-router.js.map +1 -0
- package/dist/shared/types.d.ts +485 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +80 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/assets/index-B9R4TzH7.css +32 -0
- package/dist/web/assets/index-Dh97QAgq.js +192 -0
- package/dist/web/index.html +27 -0
- package/package.json +84 -7
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const DEFAULT_MACROS = [
|
|
2
|
+
{
|
|
3
|
+
id: "run_local",
|
|
4
|
+
label: "Run local",
|
|
5
|
+
icon: "Play",
|
|
6
|
+
requiresWorkflow: true,
|
|
7
|
+
action: {
|
|
8
|
+
kind: "inject",
|
|
9
|
+
submit: true,
|
|
10
|
+
text: "cd {{workflow.path}} && sapiom agents run --target local",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "deploy",
|
|
15
|
+
label: "Deploy",
|
|
16
|
+
icon: "Cloud",
|
|
17
|
+
requiresWorkflow: true,
|
|
18
|
+
action: {
|
|
19
|
+
kind: "inject",
|
|
20
|
+
submit: true,
|
|
21
|
+
text: "cd {{workflow.path}} && sapiom agents deploy",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "prod_run",
|
|
26
|
+
label: "Prod run",
|
|
27
|
+
icon: "Zap",
|
|
28
|
+
requiresWorkflow: true,
|
|
29
|
+
action: {
|
|
30
|
+
kind: "inject",
|
|
31
|
+
submit: true,
|
|
32
|
+
text: "cd {{workflow.path}} && sapiom agents run --target prod",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "open_prod",
|
|
37
|
+
label: "Open prod",
|
|
38
|
+
icon: "ExternalLink",
|
|
39
|
+
requiresWorkflow: true,
|
|
40
|
+
action: {
|
|
41
|
+
kind: "open-url",
|
|
42
|
+
url: "https://app.sapiom.ai/workflows/{{workflow.definitionId}}",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
// One-click force refresh of the bound workflow's canvas: re-runs the
|
|
47
|
+
// deterministic, zero-LLM structure render (core/canvas-render.ts —
|
|
48
|
+
// instant, cache-invalidated) AND re-spawns the bounded AI enrichment
|
|
49
|
+
// task (core/canvas-enrich.ts, a headless background run that returns
|
|
50
|
+
// validated JSON annotations, never HTML) — all server-side, without
|
|
51
|
+
// touching the session's pty. A cheap no-op when the session is unbound.
|
|
52
|
+
id: "visualize",
|
|
53
|
+
label: "Visualize",
|
|
54
|
+
icon: "Sparkles",
|
|
55
|
+
requiresWorkflow: false,
|
|
56
|
+
action: { kind: "render-canvas" },
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
//# sourceMappingURL=macros.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macros.js","sourceRoot":"","sources":["../../src/core/macros.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,MAAM;QACZ,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,0DAA0D;SACjE;KACF;IACD;QACE,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,OAAO;QACb,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,8CAA8C;SACrD;KACF;IACD;QACE,EAAE,EAAE,UAAU;QACd,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,KAAK;QACX,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,yDAAyD;SAChE;KACF;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,cAAc;QACpB,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE;YACN,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,2DAA2D;SACjE;KACF;IACD;QACE,sEAAsE;QACtE,oEAAoE;QACpE,sEAAsE;QACtE,sEAAsE;QACtE,qEAAqE;QACrE,yEAAyE;QACzE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,UAAU;QAChB,gBAAgB,EAAE,KAAK;QACvB,MAAM,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE;KAClC;CACF,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The absolute path of `name` as a direct child of `root`, or null when
|
|
3
|
+
* `name` is anything other than one plain path segment — separators, ".",
|
|
4
|
+
* "..", absolute paths, or anything else that could resolve outside `root`.
|
|
5
|
+
* (Extracted from the generated-dir retention policy, which relies on it to
|
|
6
|
+
* refuse deleting anything but a direct child of its own root.)
|
|
7
|
+
*/
|
|
8
|
+
export declare function childPath(root: string, name: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves `candidate` (relative to `root`, or absolute) and confirms the
|
|
11
|
+
* result stays within `root`. Returns the resolved absolute path, or null if
|
|
12
|
+
* it escapes — whether via a `..` climb, an absolute path, or a different
|
|
13
|
+
* drive. `root` itself counts as inside.
|
|
14
|
+
*
|
|
15
|
+
* Containment is decided with `path.relative`: the relative path from the
|
|
16
|
+
* resolved root to the resolved candidate begins with `..` (or is itself
|
|
17
|
+
* absolute) exactly when the candidate lies outside the root — the standard,
|
|
18
|
+
* analyzer-recognized shape for a path-traversal barrier.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveWithinRoot(root: string, candidate: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* True when `p` contains an upward-traversal (`..`) path segment. Segment
|
|
23
|
+
* aware: a name that merely *contains* ".." (e.g. "a..b") is not a match.
|
|
24
|
+
*
|
|
25
|
+
* Use as a defense-in-depth assertion on an already-resolved absolute path
|
|
26
|
+
* for endpoints that legitimately accept an arbitrary absolute path (a
|
|
27
|
+
* filesystem browser, an explicit "connect this directory") and so have no
|
|
28
|
+
* single root to confine to. A fully resolved path never retains a `..`
|
|
29
|
+
* segment, so this rejects nothing legitimate — it makes the "normalization
|
|
30
|
+
* left no traversal behind" guarantee explicit and local to the sink.
|
|
31
|
+
*/
|
|
32
|
+
export declare function hasTraversalSegment(p: string): boolean;
|
|
33
|
+
//# sourceMappingURL=path-safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-safety.d.ts","sourceRoot":"","sources":["../../src/core/path-safety.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEtD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filesystem path-safety guards.
|
|
3
|
+
*
|
|
4
|
+
* The harness runs on the user's own machine on the user's own inputs
|
|
5
|
+
* (workflow directories, a session cwd, a canvas sub-path), so the threat
|
|
6
|
+
* here isn't a remote attacker crossing a privilege boundary — it's ordinary
|
|
7
|
+
* footguns: a `..` segment, an absolute path where a relative one was meant,
|
|
8
|
+
* or a symlink that quietly resolves a user-derived value outside the
|
|
9
|
+
* directory a feature intended to confine it to. Each helper turns "a path
|
|
10
|
+
* that might escape" into "a path proven to stay put, or null", so callers
|
|
11
|
+
* reject rather than silently reach outside — and each guard is written in a
|
|
12
|
+
* form static analysis recognizes as neutralizing path-traversal taint.
|
|
13
|
+
*/
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
/**
|
|
16
|
+
* The absolute path of `name` as a direct child of `root`, or null when
|
|
17
|
+
* `name` is anything other than one plain path segment — separators, ".",
|
|
18
|
+
* "..", absolute paths, or anything else that could resolve outside `root`.
|
|
19
|
+
* (Extracted from the generated-dir retention policy, which relies on it to
|
|
20
|
+
* refuse deleting anything but a direct child of its own root.)
|
|
21
|
+
*/
|
|
22
|
+
export function childPath(root, name) {
|
|
23
|
+
if (!name || name === "." || name === "..")
|
|
24
|
+
return null;
|
|
25
|
+
const resolved = path.resolve(root, name);
|
|
26
|
+
if (path.dirname(resolved) !== root || path.basename(resolved) !== name)
|
|
27
|
+
return null;
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves `candidate` (relative to `root`, or absolute) and confirms the
|
|
32
|
+
* result stays within `root`. Returns the resolved absolute path, or null if
|
|
33
|
+
* it escapes — whether via a `..` climb, an absolute path, or a different
|
|
34
|
+
* drive. `root` itself counts as inside.
|
|
35
|
+
*
|
|
36
|
+
* Containment is decided with `path.relative`: the relative path from the
|
|
37
|
+
* resolved root to the resolved candidate begins with `..` (or is itself
|
|
38
|
+
* absolute) exactly when the candidate lies outside the root — the standard,
|
|
39
|
+
* analyzer-recognized shape for a path-traversal barrier.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveWithinRoot(root, candidate) {
|
|
42
|
+
const resolvedRoot = path.resolve(root);
|
|
43
|
+
const resolved = path.resolve(resolvedRoot, candidate);
|
|
44
|
+
const relative = path.relative(resolvedRoot, resolved);
|
|
45
|
+
if (relative.startsWith("..") || path.isAbsolute(relative))
|
|
46
|
+
return null;
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* True when `p` contains an upward-traversal (`..`) path segment. Segment
|
|
51
|
+
* aware: a name that merely *contains* ".." (e.g. "a..b") is not a match.
|
|
52
|
+
*
|
|
53
|
+
* Use as a defense-in-depth assertion on an already-resolved absolute path
|
|
54
|
+
* for endpoints that legitimately accept an arbitrary absolute path (a
|
|
55
|
+
* filesystem browser, an explicit "connect this directory") and so have no
|
|
56
|
+
* single root to confine to. A fully resolved path never retains a `..`
|
|
57
|
+
* segment, so this rejects nothing legitimate — it makes the "normalization
|
|
58
|
+
* left no traversal behind" guarantee explicit and local to the sink.
|
|
59
|
+
*/
|
|
60
|
+
export function hasTraversalSegment(p) {
|
|
61
|
+
return /(?:^|[/\\])\.\.(?:[/\\]|$)/.test(p);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=path-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-safety.js","sourceRoot":"","sources":["../../src/core/path-safety.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAY;IAClD,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACrF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,SAAiB;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACxE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAS;IAC3C,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small path helper shared across the harness's core modules (session
|
|
3
|
+
* manager, adapters, analytics store): the contract (`src/shared/types.ts`)
|
|
4
|
+
* expresses well-known paths with a leading `~`.
|
|
5
|
+
*/
|
|
6
|
+
/** Expand a leading `~` (home directory) in a path, then resolve to absolute. */
|
|
7
|
+
export declare function expandHome(path: string): string;
|
|
8
|
+
/** Absolute locations of every piece of persistent harness state, all rooted
|
|
9
|
+
* under one directory. See resolveStatePaths(). */
|
|
10
|
+
export interface HarnessStatePaths {
|
|
11
|
+
root: string;
|
|
12
|
+
machineId: string;
|
|
13
|
+
sessions: string;
|
|
14
|
+
workflows: string;
|
|
15
|
+
events: string;
|
|
16
|
+
settings: string;
|
|
17
|
+
generated: string;
|
|
18
|
+
sampleProject: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the full set of persistent-state locations under a single root.
|
|
22
|
+
* Defaults to the real HARNESS_HOME (`~/.sapiom/harness`) — the CLI's
|
|
23
|
+
* behavior is unchanged — while tests and scripted checks pass a scratch
|
|
24
|
+
* directory so a server boot can never touch the developer's real state.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveStatePaths(stateRoot?: string): HarnessStatePaths;
|
|
27
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/core/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;oDACoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAUD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAYvE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small path helper shared across the harness's core modules (session
|
|
3
|
+
* manager, adapters, analytics store): the contract (`src/shared/types.ts`)
|
|
4
|
+
* expresses well-known paths with a leading `~`.
|
|
5
|
+
*/
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { HARNESS_HOME, HARNESS_PATHS } from "../shared/types.js";
|
|
9
|
+
/** Expand a leading `~` (home directory) in a path, then resolve to absolute. */
|
|
10
|
+
export function expandHome(path) {
|
|
11
|
+
if (path === "~")
|
|
12
|
+
return homedir();
|
|
13
|
+
if (path.startsWith("~/"))
|
|
14
|
+
return resolve(homedir(), path.slice(2));
|
|
15
|
+
return resolve(path);
|
|
16
|
+
}
|
|
17
|
+
/** The path of `entry` relative to HARNESS_HOME — HARNESS_PATHS expresses
|
|
18
|
+
* every well-known file as `${HARNESS_HOME}/<name>`, and deriving the name
|
|
19
|
+
* here (instead of repeating it) keeps HARNESS_PATHS the single source of
|
|
20
|
+
* truth for what the files are called. */
|
|
21
|
+
function relativeToHome(entry) {
|
|
22
|
+
return entry.slice(HARNESS_HOME.length + 1);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the full set of persistent-state locations under a single root.
|
|
26
|
+
* Defaults to the real HARNESS_HOME (`~/.sapiom/harness`) — the CLI's
|
|
27
|
+
* behavior is unchanged — while tests and scripted checks pass a scratch
|
|
28
|
+
* directory so a server boot can never touch the developer's real state.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveStatePaths(stateRoot) {
|
|
31
|
+
const root = expandHome(stateRoot ?? HARNESS_HOME);
|
|
32
|
+
return {
|
|
33
|
+
root,
|
|
34
|
+
machineId: join(root, relativeToHome(HARNESS_PATHS.machineId)),
|
|
35
|
+
sessions: join(root, relativeToHome(HARNESS_PATHS.sessions)),
|
|
36
|
+
workflows: join(root, relativeToHome(HARNESS_PATHS.workflows)),
|
|
37
|
+
events: join(root, relativeToHome(HARNESS_PATHS.events)),
|
|
38
|
+
settings: join(root, relativeToHome(HARNESS_PATHS.settings)),
|
|
39
|
+
generated: join(root, relativeToHome(HARNESS_PATHS.generated)),
|
|
40
|
+
sampleProject: join(root, relativeToHome(HARNESS_PATHS.sampleProject)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/core/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEjE,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC;AAeD;;;2CAG2C;AAC3C,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAkB;IAClD,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,IAAI,YAAY,CAAC,CAAC;IACnD,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9D,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5D,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACxD,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5D,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9D,aAAa,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;KACvE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-server port detection (workstream W5's backend slice).
|
|
3
|
+
*
|
|
4
|
+
* The Preview pane's "Preview :PORT" chip (CanvasPane.tsx) lights up from a
|
|
5
|
+
* `port.detected` bus message. This is the detector behind that signal: feed
|
|
6
|
+
* it raw text — pty output chunks as they arrive, or a `tool.call` event's
|
|
7
|
+
* command/output — per session, and it calls `onPort` once per distinct
|
|
8
|
+
* (session, port) `localhost:<port>` reference it finds. The integrator
|
|
9
|
+
* wires `feed()` to both the pty output stream and the analytics ingest
|
|
10
|
+
* pipeline, and broadcasts `{type: "port.detected", harnessSessionId, port,
|
|
11
|
+
* url}` on /ws/events from `onPort`.
|
|
12
|
+
*/
|
|
13
|
+
export interface PortDetectorDeps {
|
|
14
|
+
onPort(harnessSessionId: string, port: number, url: string): void;
|
|
15
|
+
/** Ports considered the harness's own infrastructure — a match against one
|
|
16
|
+
* of these is silently dropped (not even recorded as "seen") rather than
|
|
17
|
+
* broadcast as a discovery. Known ports at construction time; more can be
|
|
18
|
+
* added later via `addExcludedPort` (e.g. an ephemeral `port: 0` only
|
|
19
|
+
* resolves to its real bound port after the server starts listening,
|
|
20
|
+
* which happens after this detector is already constructed). */
|
|
21
|
+
excludedPorts?: Iterable<number>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extracts the port a URL string actually resolves to, including the
|
|
25
|
+
* protocol's implicit default when none is written out explicitly (`new
|
|
26
|
+
* URL(...).port` is `""` in that case, not the real port) — used to exclude
|
|
27
|
+
* the analytics collector's own port from detection.
|
|
28
|
+
*/
|
|
29
|
+
export declare function portFromUrl(rawUrl: string): number | null;
|
|
30
|
+
/**
|
|
31
|
+
* Stateful, streaming-safe: a match that touches the very end of the
|
|
32
|
+
* buffered text is held back rather than finalized, in case more digits are
|
|
33
|
+
* still arriving in the next chunk (so this is safe to feed byte-for-byte
|
|
34
|
+
* pty output, not just complete lines). Call `flush()` after feeding a
|
|
35
|
+
* discrete, already-complete string (e.g. one tool.call event's output) —
|
|
36
|
+
* see its doc comment for why that's necessary. Dedupes per (session, port)
|
|
37
|
+
* so a port that keeps appearing in output only ever fires `onPort` once —
|
|
38
|
+
* permanently for the session's lifetime (not a TTL): `reset()` is what
|
|
39
|
+
* clears it, on session exit, so a reused session id starts clean.
|
|
40
|
+
*/
|
|
41
|
+
export declare class PortDetector {
|
|
42
|
+
private readonly deps;
|
|
43
|
+
private readonly buffers;
|
|
44
|
+
private readonly seenPorts;
|
|
45
|
+
private readonly excludedPorts;
|
|
46
|
+
constructor(deps: PortDetectorDeps);
|
|
47
|
+
/** Exclude a port discovered after construction — e.g. the harness's own
|
|
48
|
+
* actual bound port, which is only known once the HTTP server resolves
|
|
49
|
+
* its `listen()` call (relevant for an ephemeral `port: 0`). */
|
|
50
|
+
addExcludedPort(port: number): void;
|
|
51
|
+
feed(chunk: string, harnessSessionId: string): void;
|
|
52
|
+
private tryEmit;
|
|
53
|
+
/**
|
|
54
|
+
* Finalizes whatever's currently held back for a session, immediately.
|
|
55
|
+
* `feed()` deliberately withholds a match touching the end of the buffered
|
|
56
|
+
* text in case more digits are still arriving — correct for a live pty
|
|
57
|
+
* byte stream, but wrong for a single discrete, complete string (e.g. one
|
|
58
|
+
* `tool.call` event's already-finished output) where no "next chunk" is
|
|
59
|
+
* ever coming: without this, a port at the very end of that string (a
|
|
60
|
+
* common shape — "...started server on http://localhost:5544") would be
|
|
61
|
+
* held pending forever and `onPort` would never fire. Call this right
|
|
62
|
+
* after `feed()` whenever the fed text is known to be complete.
|
|
63
|
+
*/
|
|
64
|
+
flush(harnessSessionId: string): void;
|
|
65
|
+
/** Drops buffered/dedupe state for a session — call on session exit/kill so
|
|
66
|
+
* a reused session id (e.g. a fresh session in the same slot) starts clean. */
|
|
67
|
+
reset(harnessSessionId: string): void;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=port-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-detector.d.ts","sourceRoot":"","sources":["../../src/core/port-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAClE;;;;;qEAKiE;IACjE,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQzD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;gBAEf,IAAI,EAAE,gBAAgB;IAInD;;qEAEiE;IACjE,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAInC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAqBnD,OAAO,CAAC,OAAO;IAiBf;;;;;;;;;;OAUG;IACH,KAAK,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAYrC;oFACgF;IAChF,KAAK,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;CAItC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-server port detection (workstream W5's backend slice).
|
|
3
|
+
*
|
|
4
|
+
* The Preview pane's "Preview :PORT" chip (CanvasPane.tsx) lights up from a
|
|
5
|
+
* `port.detected` bus message. This is the detector behind that signal: feed
|
|
6
|
+
* it raw text — pty output chunks as they arrive, or a `tool.call` event's
|
|
7
|
+
* command/output — per session, and it calls `onPort` once per distinct
|
|
8
|
+
* (session, port) `localhost:<port>` reference it finds. The integrator
|
|
9
|
+
* wires `feed()` to both the pty output stream and the analytics ingest
|
|
10
|
+
* pipeline, and broadcasts `{type: "port.detected", harnessSessionId, port,
|
|
11
|
+
* url}` on /ws/events from `onPort`.
|
|
12
|
+
*/
|
|
13
|
+
const PORT_PATTERN_SOURCE = String.raw `(?:https?://)?localhost:(\d{2,5})\b`;
|
|
14
|
+
/** Long enough to hold the literal "localhost:" anchor split across a chunk
|
|
15
|
+
* boundary, plus a few digits either side. */
|
|
16
|
+
const TAIL_SAFETY = 24;
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the port a URL string actually resolves to, including the
|
|
19
|
+
* protocol's implicit default when none is written out explicitly (`new
|
|
20
|
+
* URL(...).port` is `""` in that case, not the real port) — used to exclude
|
|
21
|
+
* the analytics collector's own port from detection.
|
|
22
|
+
*/
|
|
23
|
+
export function portFromUrl(rawUrl) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(rawUrl);
|
|
26
|
+
if (url.port)
|
|
27
|
+
return Number(url.port);
|
|
28
|
+
return url.protocol === "https:" ? 443 : 80;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stateful, streaming-safe: a match that touches the very end of the
|
|
36
|
+
* buffered text is held back rather than finalized, in case more digits are
|
|
37
|
+
* still arriving in the next chunk (so this is safe to feed byte-for-byte
|
|
38
|
+
* pty output, not just complete lines). Call `flush()` after feeding a
|
|
39
|
+
* discrete, already-complete string (e.g. one tool.call event's output) —
|
|
40
|
+
* see its doc comment for why that's necessary. Dedupes per (session, port)
|
|
41
|
+
* so a port that keeps appearing in output only ever fires `onPort` once —
|
|
42
|
+
* permanently for the session's lifetime (not a TTL): `reset()` is what
|
|
43
|
+
* clears it, on session exit, so a reused session id starts clean.
|
|
44
|
+
*/
|
|
45
|
+
export class PortDetector {
|
|
46
|
+
constructor(deps) {
|
|
47
|
+
this.deps = deps;
|
|
48
|
+
this.buffers = new Map();
|
|
49
|
+
this.seenPorts = new Map();
|
|
50
|
+
this.excludedPorts = new Set(deps.excludedPorts ?? []);
|
|
51
|
+
}
|
|
52
|
+
/** Exclude a port discovered after construction — e.g. the harness's own
|
|
53
|
+
* actual bound port, which is only known once the HTTP server resolves
|
|
54
|
+
* its `listen()` call (relevant for an ephemeral `port: 0`). */
|
|
55
|
+
addExcludedPort(port) {
|
|
56
|
+
this.excludedPorts.add(port);
|
|
57
|
+
}
|
|
58
|
+
feed(chunk, harnessSessionId) {
|
|
59
|
+
const combined = (this.buffers.get(harnessSessionId) ?? "") + chunk;
|
|
60
|
+
const regex = new RegExp(PORT_PATTERN_SOURCE, "g");
|
|
61
|
+
let match;
|
|
62
|
+
let pendingFrom = null;
|
|
63
|
+
while ((match = regex.exec(combined))) {
|
|
64
|
+
const end = match.index + match[0].length;
|
|
65
|
+
if (end === combined.length) {
|
|
66
|
+
// Could still be growing (more digits due next chunk) — hold it back.
|
|
67
|
+
pendingFrom = match.index;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
this.tryEmit(harnessSessionId, Number(match[1]));
|
|
71
|
+
}
|
|
72
|
+
const retainFrom = pendingFrom ?? Math.max(0, combined.length - TAIL_SAFETY);
|
|
73
|
+
this.buffers.set(harnessSessionId, combined.slice(retainFrom));
|
|
74
|
+
}
|
|
75
|
+
tryEmit(harnessSessionId, port) {
|
|
76
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535)
|
|
77
|
+
return;
|
|
78
|
+
// Never even recorded as "seen" — an excluded port isn't a real
|
|
79
|
+
// discovery, so it shouldn't consume per-session dedupe state either.
|
|
80
|
+
if (this.excludedPorts.has(port))
|
|
81
|
+
return;
|
|
82
|
+
let ports = this.seenPorts.get(harnessSessionId);
|
|
83
|
+
if (!ports) {
|
|
84
|
+
ports = new Set();
|
|
85
|
+
this.seenPorts.set(harnessSessionId, ports);
|
|
86
|
+
}
|
|
87
|
+
if (ports.has(port))
|
|
88
|
+
return;
|
|
89
|
+
ports.add(port);
|
|
90
|
+
this.deps.onPort(harnessSessionId, port, `http://localhost:${port}`);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Finalizes whatever's currently held back for a session, immediately.
|
|
94
|
+
* `feed()` deliberately withholds a match touching the end of the buffered
|
|
95
|
+
* text in case more digits are still arriving — correct for a live pty
|
|
96
|
+
* byte stream, but wrong for a single discrete, complete string (e.g. one
|
|
97
|
+
* `tool.call` event's already-finished output) where no "next chunk" is
|
|
98
|
+
* ever coming: without this, a port at the very end of that string (a
|
|
99
|
+
* common shape — "...started server on http://localhost:5544") would be
|
|
100
|
+
* held pending forever and `onPort` would never fire. Call this right
|
|
101
|
+
* after `feed()` whenever the fed text is known to be complete.
|
|
102
|
+
*/
|
|
103
|
+
flush(harnessSessionId) {
|
|
104
|
+
const pending = this.buffers.get(harnessSessionId);
|
|
105
|
+
if (!pending)
|
|
106
|
+
return;
|
|
107
|
+
const regex = new RegExp(PORT_PATTERN_SOURCE, "g");
|
|
108
|
+
let match;
|
|
109
|
+
while ((match = regex.exec(pending))) {
|
|
110
|
+
this.tryEmit(harnessSessionId, Number(match[1]));
|
|
111
|
+
}
|
|
112
|
+
this.buffers.delete(harnessSessionId);
|
|
113
|
+
}
|
|
114
|
+
/** Drops buffered/dedupe state for a session — call on session exit/kill so
|
|
115
|
+
* a reused session id (e.g. a fresh session in the same slot) starts clean. */
|
|
116
|
+
reset(harnessSessionId) {
|
|
117
|
+
this.buffers.delete(harnessSessionId);
|
|
118
|
+
this.seenPorts.delete(harnessSessionId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=port-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-detector.js","sourceRoot":"","sources":["../../src/core/port-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA,qCAAqC,CAAC;AAE5E;+CAC+C;AAC/C,MAAM,WAAW,GAAG,EAAE,CAAC;AAavB;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,GAAG,CAAC,IAAI;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IAKvB,YAA6B,IAAsB;QAAtB,SAAI,GAAJ,IAAI,CAAkB;QAJlC,YAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QACpC,cAAS,GAAG,IAAI,GAAG,EAAuB,CAAC;QAI1D,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAED;;qEAEiE;IACjE,eAAe,CAAC,IAAY;QAC1B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,CAAC,KAAa,EAAE,gBAAwB;QAC1C,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC;QACpE,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;QAEnD,IAAI,KAA6B,CAAC;QAClC,IAAI,WAAW,GAAkB,IAAI,CAAC;QAEtC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1C,IAAI,GAAG,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAC5B,sEAAsE;gBACtE,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC;gBAC1B,MAAM;YACR,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC;QAED,MAAM,UAAU,GAAG,WAAW,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;QAC7E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IACjE,CAAC;IAEO,OAAO,CAAC,gBAAwB,EAAE,IAAY;QACpD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK;YAAE,OAAO;QACjE,gEAAgE;QAChE,sEAAsE;QACtE,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAEzC,IAAI,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAC5B,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEhB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,gBAAwB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,KAA6B,CAAC;QAClC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACxC,CAAC;IAED;oFACgF;IAChF,KAAK,CAAC,gBAAwB;QAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACtC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;CACF"}
|