@geohar/un-bien 0.7.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/LICENSE +21 -0
- package/README.md +726 -0
- package/dist/actions/handlers.d.ts +77 -0
- package/dist/actions/handlers.js +26 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +39 -0
- package/dist/actions/registry.js +62 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/bin/supervisord.d.ts +2 -0
- package/dist/bin/supervisord.js +86 -0
- package/dist/bin/supervisord.js.map +1 -0
- package/dist/config.d.ts +84 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/client.d.ts +20 -0
- package/dist/daemon/client.js +131 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/control_protocol.d.ts +176 -0
- package/dist/daemon/control_protocol.js +63 -0
- package/dist/daemon/control_protocol.js.map +1 -0
- package/dist/daemon/cron_log.d.ts +45 -0
- package/dist/daemon/cron_log.js +71 -0
- package/dist/daemon/cron_log.js.map +1 -0
- package/dist/daemon/cron_registry.d.ts +80 -0
- package/dist/daemon/cron_registry.js +198 -0
- package/dist/daemon/cron_registry.js.map +1 -0
- package/dist/daemon/id.d.ts +24 -0
- package/dist/daemon/id.js +36 -0
- package/dist/daemon/id.js.map +1 -0
- package/dist/daemon/install.d.ts +170 -0
- package/dist/daemon/install.js +603 -0
- package/dist/daemon/install.js.map +1 -0
- package/dist/daemon/registry.d.ts +63 -0
- package/dist/daemon/registry.js +153 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/rpc_child.d.ts +194 -0
- package/dist/daemon/rpc_child.js +419 -0
- package/dist/daemon/rpc_child.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +93 -0
- package/dist/daemon/supervisor.js +666 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/extension_ui_bridge.d.ts +26 -0
- package/dist/extension_ui_bridge.js +341 -0
- package/dist/extension_ui_bridge.js.map +1 -0
- package/dist/index.d.ts +197 -0
- package/dist/index.js +5750 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/mesh_result.d.ts +10 -0
- package/dist/mcp/mesh_result.js +42 -0
- package/dist/mcp/mesh_result.js.map +1 -0
- package/dist/mcp/mesh_server.d.ts +16 -0
- package/dist/mcp/mesh_server.js +373 -0
- package/dist/mcp/mesh_server.js.map +1 -0
- package/dist/mesh/canonical.d.ts +30 -0
- package/dist/mesh/canonical.js +63 -0
- package/dist/mesh/canonical.js.map +1 -0
- package/dist/mesh/client.d.ts +18 -0
- package/dist/mesh/client.js +127 -0
- package/dist/mesh/client.js.map +1 -0
- package/dist/mesh/encoding.d.ts +38 -0
- package/dist/mesh/encoding.js +217 -0
- package/dist/mesh/encoding.js.map +1 -0
- package/dist/mesh/self_revoke.d.ts +73 -0
- package/dist/mesh/self_revoke.js +407 -0
- package/dist/mesh/self_revoke.js.map +1 -0
- package/dist/mesh/siblings.d.ts +49 -0
- package/dist/mesh/siblings.js +159 -0
- package/dist/mesh/siblings.js.map +1 -0
- package/dist/mesh/types.d.ts +34 -0
- package/dist/mesh/types.js +11 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/mesh/verify.d.ts +17 -0
- package/dist/mesh/verify.js +86 -0
- package/dist/mesh/verify.js.map +1 -0
- package/dist/pairing/crypto.d.ts +8 -0
- package/dist/pairing/crypto.js +22 -0
- package/dist/pairing/crypto.js.map +1 -0
- package/dist/pairing/qr.d.ts +59 -0
- package/dist/pairing/qr.js +127 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +120 -0
- package/dist/pairing/storage.js +614 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/panel_bridge.d.ts +15 -0
- package/dist/panel_bridge.js +409 -0
- package/dist/panel_bridge.js.map +1 -0
- package/dist/paths.d.ts +39 -0
- package/dist/paths.js +51 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/codec.d.ts +7 -0
- package/dist/protocol/codec.js +48 -0
- package/dist/protocol/codec.js.map +1 -0
- package/dist/protocol/types.d.ts +404 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/rooms.d.ts +42 -0
- package/dist/rooms.js +78 -0
- package/dist/rooms.js.map +1 -0
- package/dist/session/bridge.d.ts +37 -0
- package/dist/session/bridge.js +134 -0
- package/dist/session/bridge.js.map +1 -0
- package/dist/session/broker.d.ts +189 -0
- package/dist/session/broker.js +527 -0
- package/dist/session/broker.js.map +1 -0
- package/dist/session/broker_remote.d.ts +74 -0
- package/dist/session/broker_remote.js +581 -0
- package/dist/session/broker_remote.js.map +1 -0
- package/dist/session/cwd_lock.d.ts +33 -0
- package/dist/session/cwd_lock.js +127 -0
- package/dist/session/cwd_lock.js.map +1 -0
- package/dist/session/debug_log.d.ts +3 -0
- package/dist/session/debug_log.js +32 -0
- package/dist/session/debug_log.js.map +1 -0
- package/dist/session/envelope.d.ts +33 -0
- package/dist/session/envelope.js +117 -0
- package/dist/session/envelope.js.map +1 -0
- package/dist/session/global_config.d.ts +33 -0
- package/dist/session/global_config.js +73 -0
- package/dist/session/global_config.js.map +1 -0
- package/dist/session/ipc.d.ts +27 -0
- package/dist/session/ipc.js +22 -0
- package/dist/session/ipc.js.map +1 -0
- package/dist/session/leader_election.d.ts +32 -0
- package/dist/session/leader_election.js +108 -0
- package/dist/session/leader_election.js.map +1 -0
- package/dist/session/local_config.d.ts +67 -0
- package/dist/session/local_config.js +193 -0
- package/dist/session/local_config.js.map +1 -0
- package/dist/session/mesh_node.d.ts +116 -0
- package/dist/session/mesh_node.js +596 -0
- package/dist/session/mesh_node.js.map +1 -0
- package/dist/session/peer.d.ts +139 -0
- package/dist/session/peer.js +474 -0
- package/dist/session/peer.js.map +1 -0
- package/dist/session/peer_inventory.d.ts +13 -0
- package/dist/session/peer_inventory.js +48 -0
- package/dist/session/peer_inventory.js.map +1 -0
- package/dist/session/peer_limits.d.ts +9 -0
- package/dist/session/peer_limits.js +24 -0
- package/dist/session/peer_limits.js.map +1 -0
- package/dist/session/rpc_envelope.d.ts +108 -0
- package/dist/session/rpc_envelope.js +221 -0
- package/dist/session/rpc_envelope.js.map +1 -0
- package/dist/session/rpc_inbound.d.ts +55 -0
- package/dist/session/rpc_inbound.js +136 -0
- package/dist/session/rpc_inbound.js.map +1 -0
- package/dist/session/setup_wizard.d.ts +39 -0
- package/dist/session/setup_wizard.js +63 -0
- package/dist/session/setup_wizard.js.map +1 -0
- package/dist/session/tools.d.ts +24 -0
- package/dist/session/tools.js +272 -0
- package/dist/session/tools.js.map +1 -0
- package/dist/session/wizard.d.ts +13 -0
- package/dist/session/wizard.js +20 -0
- package/dist/session/wizard.js.map +1 -0
- package/dist/transport/peer_channel.d.ts +52 -0
- package/dist/transport/peer_channel.js +145 -0
- package/dist/transport/peer_channel.js.map +1 -0
- package/dist/transport/pi_forward_client.d.ts +29 -0
- package/dist/transport/pi_forward_client.js +68 -0
- package/dist/transport/pi_forward_client.js.map +1 -0
- package/dist/transport/relay_client.d.ts +96 -0
- package/dist/transport/relay_client.js +210 -0
- package/dist/transport/relay_client.js.map +1 -0
- package/dist/ui/footer.d.ts +32 -0
- package/dist/ui/footer.js +34 -0
- package/dist/ui/footer.js.map +1 -0
- package/docs/daemon.md +289 -0
- package/package.json +92 -0
- package/service-templates/launchd.plist.template +35 -0
- package/service-templates/systemd.service.template +19 -0
- package/service-templates/task-launcher.vbs.template +10 -0
- package/service-templates/task-scheduler.xml.template +38 -0
- package/skills/agent-network/SKILL.md +310 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model registry projection + the narrow SDK/ctx views the rpc command
|
|
3
|
+
* dispatch (`index.ts`) uses for `set_model` / `get_available_models` /
|
|
4
|
+
* `compact` / `new_session`.
|
|
5
|
+
*
|
|
6
|
+
* The former stock action handlers (session_compact/new/model_set/thinking_set/
|
|
7
|
+
* list_models) were retired when those app commands migrated to the rpc plane
|
|
8
|
+
* (dispatchRpcCommand). Only the shared model types + `wireFromModel` + the
|
|
9
|
+
* narrow ctx/pi/registry views the rpc handlers still take remain.
|
|
10
|
+
*/
|
|
11
|
+
import type { WireModel } from "../protocol/types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Structural subset of the SDK's `Model<Api>` interface (defined in
|
|
14
|
+
* `@earendil-works/pi-ai`, a transitive dep not re-exported by
|
|
15
|
+
* `@earendil-works/pi-coding-agent`'s main entry). Capturing just the fields we
|
|
16
|
+
* touch keeps this decoupled from the SDK's full Model surface.
|
|
17
|
+
*/
|
|
18
|
+
export interface SdkModelLike {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
provider: string;
|
|
22
|
+
reasoning: boolean;
|
|
23
|
+
contextWindow: number;
|
|
24
|
+
/** Plan/30: accepted input modalities. The SDK's `Model.input` is
|
|
25
|
+
* `("text" | "image")[]`; we read `includes("image")` for the `vision`
|
|
26
|
+
* flag. Optional here so tests can omit it (treated as text-only). */
|
|
27
|
+
input?: ("text" | "image")[];
|
|
28
|
+
}
|
|
29
|
+
type Model<_TApi = unknown> = SdkModelLike;
|
|
30
|
+
/**
|
|
31
|
+
* Narrow shape of the `ExtensionAPI` surface the rpc dispatch calls (setModel /
|
|
32
|
+
* setThinkingLevel). Lets the test layer stub just these.
|
|
33
|
+
*/
|
|
34
|
+
export interface ActionPi {
|
|
35
|
+
setModel(model: Model<any>): Promise<boolean>;
|
|
36
|
+
setThinkingLevel(level: import("../protocol/types.js").ThinkingLevel): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Narrow shape of the per-call context. Drawn from the union of
|
|
40
|
+
* `ExtensionContextActions` (compact, getModel) and
|
|
41
|
+
* `ExtensionCommandContextActions` (newSession). All fields optional so a
|
|
42
|
+
* missing method becomes a typed error instead of a runtime TypeError.
|
|
43
|
+
*/
|
|
44
|
+
export interface ActionCtx {
|
|
45
|
+
compact?: (options?: Record<string, unknown>) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Starts a new session. `withSession` is the SDK's blessed hook for
|
|
48
|
+
* post-replacement work: it receives a FRESH, command-capable ctx bound to
|
|
49
|
+
* the new session. The SDK marks any ctx captured BEFORE this call stale, so
|
|
50
|
+
* callers must re-capture via `withSession` rather than reuse the old ctx.
|
|
51
|
+
*/
|
|
52
|
+
newSession?: (options?: {
|
|
53
|
+
withSession?: (ctx: ActionCtx) => Promise<void>;
|
|
54
|
+
}) => Promise<{
|
|
55
|
+
cancelled: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
getModel?: () => Model<any> | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Live session registry from Pi's extension ctx. Includes providers/models
|
|
60
|
+
* registered dynamically via `pi.registerProvider(...)`, unlike the fallback
|
|
61
|
+
* disk-backed registry un-bien can build on its own.
|
|
62
|
+
*/
|
|
63
|
+
modelRegistry?: ActionModelRegistry;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Minimal shape of the registry surface. Maps 1:1 onto `ModelRegistry` but lets
|
|
67
|
+
* tests fake catalogs without instantiating the real one.
|
|
68
|
+
*/
|
|
69
|
+
export interface ActionModelRegistry {
|
|
70
|
+
refresh(): void;
|
|
71
|
+
getAvailable(): Model<any>[];
|
|
72
|
+
find(provider: string, modelId: string): Model<any> | undefined;
|
|
73
|
+
}
|
|
74
|
+
/** Project a SDK `Model<Api>` onto the wire schema. Shared by the rpc
|
|
75
|
+
* `get_available_models` / `set_model` dispatch so both stay in lockstep. */
|
|
76
|
+
export declare function wireFromModel(model: Model<any>): WireModel;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model registry projection + the narrow SDK/ctx views the rpc command
|
|
3
|
+
* dispatch (`index.ts`) uses for `set_model` / `get_available_models` /
|
|
4
|
+
* `compact` / `new_session`.
|
|
5
|
+
*
|
|
6
|
+
* The former stock action handlers (session_compact/new/model_set/thinking_set/
|
|
7
|
+
* list_models) were retired when those app commands migrated to the rpc plane
|
|
8
|
+
* (dispatchRpcCommand). Only the shared model types + `wireFromModel` + the
|
|
9
|
+
* narrow ctx/pi/registry views the rpc handlers still take remain.
|
|
10
|
+
*/
|
|
11
|
+
/** Project a SDK `Model<Api>` onto the wire schema. Shared by the rpc
|
|
12
|
+
* `get_available_models` / `set_model` dispatch so both stay in lockstep. */
|
|
13
|
+
export function wireFromModel(model) {
|
|
14
|
+
return {
|
|
15
|
+
id: model.id,
|
|
16
|
+
name: model.name,
|
|
17
|
+
provider: model.provider,
|
|
18
|
+
reasoning: model.reasoning,
|
|
19
|
+
context_window: model.contextWindow,
|
|
20
|
+
// Plan/30: vision = model accepts image input. `Model.input` is
|
|
21
|
+
// `("text" | "image")[]` at runtime (confirmed against pi-ai). `?.` guards
|
|
22
|
+
// a fake/partial model in tests → treated as text-only.
|
|
23
|
+
vision: model.input?.includes("image") ?? false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/actions/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuEH;8EAC8E;AAC9E,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,cAAc,EAAE,KAAK,CAAC,aAAa;QACnC,gEAAgE;QAChE,2EAA2E;QAC3E,wDAAwD;QACxD,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK;KAChD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan/28 — ModelRegistry access for the action handlers.
|
|
3
|
+
*
|
|
4
|
+
* The host hands every extension a ready `ModelRegistry` on
|
|
5
|
+
* `ExtensionContext.modelRegistry` — the same instance `AgentSession` uses
|
|
6
|
+
* internally. That is the ONLY source we read.
|
|
7
|
+
*
|
|
8
|
+
* History (issue #112): this module used to build its own registry via
|
|
9
|
+
* `ModelRegistry.create(AuthStorage.create())`. Both factories were removed
|
|
10
|
+
* from `@earendil-works/pi-coding-agent` in pi 0.83, so the import crashed the
|
|
11
|
+
* whole pi process on the first `model_set`/`list_models` from the app
|
|
12
|
+
* (`undefined is not an object (evaluating 'AuthStorage.create')`, thrown from
|
|
13
|
+
* the WebSocket line handler). Reading the registry off the live ctx drops the
|
|
14
|
+
* dependency on that package's factory surface entirely, so a future breaking
|
|
15
|
+
* change there cannot crash the extension the same way again.
|
|
16
|
+
*
|
|
17
|
+
* Two hazards this handles:
|
|
18
|
+
*
|
|
19
|
+
* 1. A ctx captured before a session replacement/reload is **stale**, and even
|
|
20
|
+
* *reading* `.modelRegistry` on it throws (`assertActive`). Every read is
|
|
21
|
+
* wrapped.
|
|
22
|
+
* 2. There may be no live ctx at all (daemon boot, control channel). Rather
|
|
23
|
+
* than throw from a WS callback — an uncaught exception that exits pi — we
|
|
24
|
+
* fall back to the last registry that worked, then to a stub whose methods
|
|
25
|
+
* never throw and report an empty catalog.
|
|
26
|
+
*/
|
|
27
|
+
import type { ActionModelRegistry } from "./handlers.js";
|
|
28
|
+
/** Anything carrying the host registry — `ExtensionContext` and friends. */
|
|
29
|
+
interface RegistryBearingCtx {
|
|
30
|
+
modelRegistry?: ActionModelRegistry;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the live host `ModelRegistry` from the most recent extension ctx.
|
|
34
|
+
* Falls back to the last one that worked, then to an inert stub.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureModelRegistry(ctx?: RegistryBearingCtx | null): ActionModelRegistry;
|
|
37
|
+
/** Test seam — drop the cached registry so tests can rebuild with fakes. */
|
|
38
|
+
export declare function _resetModelRegistryForTests(): void;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan/28 — ModelRegistry access for the action handlers.
|
|
3
|
+
*
|
|
4
|
+
* The host hands every extension a ready `ModelRegistry` on
|
|
5
|
+
* `ExtensionContext.modelRegistry` — the same instance `AgentSession` uses
|
|
6
|
+
* internally. That is the ONLY source we read.
|
|
7
|
+
*
|
|
8
|
+
* History (issue #112): this module used to build its own registry via
|
|
9
|
+
* `ModelRegistry.create(AuthStorage.create())`. Both factories were removed
|
|
10
|
+
* from `@earendil-works/pi-coding-agent` in pi 0.83, so the import crashed the
|
|
11
|
+
* whole pi process on the first `model_set`/`list_models` from the app
|
|
12
|
+
* (`undefined is not an object (evaluating 'AuthStorage.create')`, thrown from
|
|
13
|
+
* the WebSocket line handler). Reading the registry off the live ctx drops the
|
|
14
|
+
* dependency on that package's factory surface entirely, so a future breaking
|
|
15
|
+
* change there cannot crash the extension the same way again.
|
|
16
|
+
*
|
|
17
|
+
* Two hazards this handles:
|
|
18
|
+
*
|
|
19
|
+
* 1. A ctx captured before a session replacement/reload is **stale**, and even
|
|
20
|
+
* *reading* `.modelRegistry` on it throws (`assertActive`). Every read is
|
|
21
|
+
* wrapped.
|
|
22
|
+
* 2. There may be no live ctx at all (daemon boot, control channel). Rather
|
|
23
|
+
* than throw from a WS callback — an uncaught exception that exits pi — we
|
|
24
|
+
* fall back to the last registry that worked, then to a stub whose methods
|
|
25
|
+
* never throw and report an empty catalog.
|
|
26
|
+
*/
|
|
27
|
+
let _lastGoodRegistry = null;
|
|
28
|
+
/**
|
|
29
|
+
* Never-throwing empty catalog. Used only when no ctx has ever been seen —
|
|
30
|
+
* callers get an empty model list instead of a crashed agent.
|
|
31
|
+
*/
|
|
32
|
+
const _stubRegistry = {
|
|
33
|
+
refresh() { },
|
|
34
|
+
getAvailable() {
|
|
35
|
+
return [];
|
|
36
|
+
},
|
|
37
|
+
find() {
|
|
38
|
+
return undefined;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the live host `ModelRegistry` from the most recent extension ctx.
|
|
43
|
+
* Falls back to the last one that worked, then to an inert stub.
|
|
44
|
+
*/
|
|
45
|
+
export function ensureModelRegistry(ctx) {
|
|
46
|
+
try {
|
|
47
|
+
const live = ctx?.modelRegistry;
|
|
48
|
+
if (live) {
|
|
49
|
+
_lastGoodRegistry = live;
|
|
50
|
+
return live;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// stale ctx after session replacement — reading the property throws.
|
|
55
|
+
}
|
|
56
|
+
return _lastGoodRegistry ?? _stubRegistry;
|
|
57
|
+
}
|
|
58
|
+
/** Test seam — drop the cached registry so tests can rebuild with fakes. */
|
|
59
|
+
export function _resetModelRegistryForTests() {
|
|
60
|
+
_lastGoodRegistry = null;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/actions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,IAAI,iBAAiB,GAA+B,IAAI,CAAC;AAEzD;;;GAGG;AACH,MAAM,aAAa,GAAwB;IACzC,OAAO,KAAI,CAAC;IACZ,YAAY;QACV,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAA+B;IAE/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,EAAE,aAAa,CAAC;QAChC,IAAI,IAAI,EAAE,CAAC;YACT,iBAAiB,GAAG,IAAI,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;IACvE,CAAC;IACD,OAAO,iBAAiB,IAAI,aAAa,CAAC;AAC5C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,2BAA2B;IACzC,iBAAiB,GAAG,IAAI,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `pi-supervisord` — long-running daemon supervisor.
|
|
4
|
+
*
|
|
5
|
+
* Entry point of the `pi-supervisord` binary (plan/26 W2). Run by
|
|
6
|
+
* systemd/launchd in production, or directly during dev:
|
|
7
|
+
*
|
|
8
|
+
* pnpm build
|
|
9
|
+
* node dist/bin/supervisord.js
|
|
10
|
+
*
|
|
11
|
+
* Once running, it:
|
|
12
|
+
* - Reads `~/.pi/un-bien/daemons.json`
|
|
13
|
+
* - Spawns `pi --mode rpc -e <un-bien/dist/index.js>` per entry
|
|
14
|
+
* - Listens on `~/.pi/un-bien/supervisor.sock` for CLI control requests
|
|
15
|
+
* - Restarts crashed children with exponential backoff
|
|
16
|
+
*
|
|
17
|
+
* Exits cleanly on SIGTERM/SIGINT (used by `unbien uninstall`).
|
|
18
|
+
*/
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { Supervisor, SupervisorAlreadyRunningError, } from "../daemon/supervisor.js";
|
|
22
|
+
const HELP_TEXT = `pi-supervisord — Remote Pi daemon supervisor
|
|
23
|
+
|
|
24
|
+
Usage: pi-supervisord
|
|
25
|
+
|
|
26
|
+
Runs the long-lived supervisor: reads ~/.pi/un-bien/daemons.json, spawns one
|
|
27
|
+
\`pi --mode rpc\` child per entry, and listens on ~/.pi/un-bien/supervisor.sock
|
|
28
|
+
for control requests from the \`un-bien\` CLI.
|
|
29
|
+
|
|
30
|
+
This binary takes NO arguments — it is normally launched by systemd/launchd
|
|
31
|
+
(via \`unbien install\`), not by hand. Manage the fleet with:
|
|
32
|
+
unbien daemon start | stop | restart | status
|
|
33
|
+
unbien daemons
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-h, --help Show this help and exit
|
|
37
|
+
-v, --version Print version and exit
|
|
38
|
+
`;
|
|
39
|
+
async function main() {
|
|
40
|
+
// Guard: any stray argument used to fall through and start a FULL
|
|
41
|
+
// supervisor (a `pi-supervisord --help` once ran for days). Handle the
|
|
42
|
+
// conventional flags explicitly and reject unknown args instead of
|
|
43
|
+
// silently spawning the daemon fleet.
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
46
|
+
process.stdout.write(HELP_TEXT);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
50
|
+
process.stdout.write("pi-supervisord (un-bien)\n");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (args.length > 0) {
|
|
54
|
+
process.stderr.write(`pi-supervisord: unexpected argument(s): ${args.join(" ")}\n` +
|
|
55
|
+
"This binary takes no arguments. Run `pi-supervisord --help`.\n");
|
|
56
|
+
process.exit(2);
|
|
57
|
+
}
|
|
58
|
+
// The supervisor needs to point each spawned Pi at the extension
|
|
59
|
+
// entry it's bundled with. We're at `dist/bin/supervisord.js` after
|
|
60
|
+
// build; the extension is the sibling `dist/index.js`.
|
|
61
|
+
const here = fileURLToPath(import.meta.url);
|
|
62
|
+
const distRoot = dirname(dirname(here)); // dist/bin → dist
|
|
63
|
+
const extensionPath = join(distRoot, "index.js");
|
|
64
|
+
const supervisor = new Supervisor({ extensionPath });
|
|
65
|
+
await supervisor.start();
|
|
66
|
+
process.stderr.write(`[pi-supervisord] up — UDS: ~/.pi/un-bien/supervisor.sock, extension: ${extensionPath}\n`);
|
|
67
|
+
const shutdown = async (signal) => {
|
|
68
|
+
process.stderr.write(`[pi-supervisord] received ${signal}, shutting down\n`);
|
|
69
|
+
await supervisor.stop();
|
|
70
|
+
process.exit(0);
|
|
71
|
+
};
|
|
72
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
73
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
74
|
+
}
|
|
75
|
+
main().catch((err) => {
|
|
76
|
+
// "Already running" is a normal, expected condition (systemd/launchd may
|
|
77
|
+
// race a manual start, or the user double-launches) — not a crash. Report
|
|
78
|
+
// it calmly and exit 0 so service managers don't flag a failure loop.
|
|
79
|
+
if (err instanceof SupervisorAlreadyRunningError) {
|
|
80
|
+
process.stderr.write(`[pi-supervisord] ${err.message}\n`);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write(`[pi-supervisord] fatal: ${String(err)}\n`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=supervisord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisord.js","sourceRoot":"","sources":["../../src/bin/supervisord.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type UnBienConfig = {
|
|
2
|
+
relay?: string;
|
|
3
|
+
/**
|
|
4
|
+
* Debug prefs. Fields here gate the file-based diagnostic logs (see
|
|
5
|
+
* `session/debug_log.ts` and `panel_bridge.ts`). Read from config — NOT env —
|
|
6
|
+
* because the fork usually runs detached and doesn't inherit a shell's env, so
|
|
7
|
+
* a persisted config field is the only reliable enable switch. Absent/false by
|
|
8
|
+
* default: no logging.
|
|
9
|
+
*/
|
|
10
|
+
debug?: {
|
|
11
|
+
envelope?: boolean;
|
|
12
|
+
panels?: boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Machine-wide fallback defaults for a session's LOCAL config (the per-cwd
|
|
16
|
+
* `.pi/un-bien/config.json`). A field here applies to every cwd that does
|
|
17
|
+
* not set it via the inline `UNBIEN_DIRECT_CONFIG` env or its own file, so
|
|
18
|
+
* you can pin `auto_start_relay` once — beside `relay` — instead of dropping a
|
|
19
|
+
* file into every repo. See `session/local_config.ts`. Absent by default, so
|
|
20
|
+
* omitting it preserves the historical per-cwd-only behaviour exactly.
|
|
21
|
+
*/
|
|
22
|
+
defaults?: {
|
|
23
|
+
auto_start_relay?: boolean;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare function loadConfig(): UnBienConfig;
|
|
27
|
+
export declare function saveConfig(patch: Partial<UnBienConfig>): void;
|
|
28
|
+
export type RelayResolution = {
|
|
29
|
+
url: string;
|
|
30
|
+
source: "env" | "config";
|
|
31
|
+
} | {
|
|
32
|
+
url: null;
|
|
33
|
+
source: "unset";
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the effective relay URL in **canonical http(s):// form**.
|
|
37
|
+
*
|
|
38
|
+
* Precedence:
|
|
39
|
+
* 1. `UNBIEN_RELAY` env var (ops/CI escape hatch)
|
|
40
|
+
* 2. `relay` field in the global config
|
|
41
|
+
* (`<PI_CODING_AGENT_DIR|~/.pi>/extensions/un-bien.json`, set via
|
|
42
|
+
* `/unbien set-relay`)
|
|
43
|
+
*
|
|
44
|
+
* There is NO built-in default. When neither is set, `url` is null and
|
|
45
|
+
* `source` is `"unset"` — callers MUST refuse to connect and prompt the user
|
|
46
|
+
* to configure a relay (un-bien ships pointing at nobody's infrastructure).
|
|
47
|
+
*
|
|
48
|
+
* Any ws(s):// values found (legacy configs or env overrides) are coerced
|
|
49
|
+
* to http(s):// defensively — the canonical form across the codebase is
|
|
50
|
+
* http(s)://, and the transport layer converts to ws(s):// at WS-open time.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveRelayUrl(): RelayResolution;
|
|
53
|
+
/**
|
|
54
|
+
* Strict validator for **user-provided** relay URLs (via `/unbien
|
|
55
|
+
* set-relay` or `/unbien relay url`).
|
|
56
|
+
*
|
|
57
|
+
* Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
|
|
58
|
+
* **rejected** — the canonical form stored in config is http(s):// and the
|
|
59
|
+
* extension converts to ws(s):// internally when opening the WebSocket.
|
|
60
|
+
* Forcing a single scheme at the user boundary avoids two-form drift.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isValidRelayUrl(url: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Returns true if the URL uses ws:// or wss:// scheme — for emitting a
|
|
65
|
+
* targeted error message when the user pastes a WebSocket URL by mistake.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isWebSocketScheme(url: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Converts an http(s):// URL to the corresponding ws(s):// form. Used by
|
|
70
|
+
* the transport layer right before opening the WebSocket — config storage
|
|
71
|
+
* and the mesh HTTP client both stay on http(s)://.
|
|
72
|
+
*
|
|
73
|
+
* https://host → wss://host
|
|
74
|
+
* http://host → ws://host
|
|
75
|
+
* ws(s)://host → pass-through (defensive — env overrides or legacy
|
|
76
|
+
* configs may still carry ws(s)://)
|
|
77
|
+
*/
|
|
78
|
+
export declare function toWebSocketUrl(url: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
|
|
81
|
+
* ws(s):// values back to canonical http(s):// before returning them to
|
|
82
|
+
* the rest of the codebase.
|
|
83
|
+
*/
|
|
84
|
+
export declare function toHttpUrl(url: string): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { unbienConfigHome } from "./paths.js";
|
|
4
|
+
// Resolved at call time via `unbienConfigHome()` — the global config is a pi
|
|
5
|
+
// extension config at `<PI_CODING_AGENT_DIR|~/.pi>/extensions/un-bien.json`.
|
|
6
|
+
// See paths.ts.
|
|
7
|
+
const configDir = () => unbienConfigHome();
|
|
8
|
+
const configFile = () => path.join(unbienConfigHome(), "un-bien.json");
|
|
9
|
+
export function loadConfig() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = fs.readFileSync(configFile(), "utf8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (!parsed || typeof parsed !== "object")
|
|
14
|
+
return {};
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function saveConfig(patch) {
|
|
22
|
+
fs.mkdirSync(configDir(), { recursive: true });
|
|
23
|
+
const current = loadConfig();
|
|
24
|
+
const next = { ...current, ...patch };
|
|
25
|
+
fs.writeFileSync(configFile(), JSON.stringify(next, null, 2));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the effective relay URL in **canonical http(s):// form**.
|
|
29
|
+
*
|
|
30
|
+
* Precedence:
|
|
31
|
+
* 1. `UNBIEN_RELAY` env var (ops/CI escape hatch)
|
|
32
|
+
* 2. `relay` field in the global config
|
|
33
|
+
* (`<PI_CODING_AGENT_DIR|~/.pi>/extensions/un-bien.json`, set via
|
|
34
|
+
* `/unbien set-relay`)
|
|
35
|
+
*
|
|
36
|
+
* There is NO built-in default. When neither is set, `url` is null and
|
|
37
|
+
* `source` is `"unset"` — callers MUST refuse to connect and prompt the user
|
|
38
|
+
* to configure a relay (un-bien ships pointing at nobody's infrastructure).
|
|
39
|
+
*
|
|
40
|
+
* Any ws(s):// values found (legacy configs or env overrides) are coerced
|
|
41
|
+
* to http(s):// defensively — the canonical form across the codebase is
|
|
42
|
+
* http(s)://, and the transport layer converts to ws(s):// at WS-open time.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveRelayUrl() {
|
|
45
|
+
const env = process.env["UNBIEN_RELAY"];
|
|
46
|
+
if (env && env.length > 0)
|
|
47
|
+
return { url: toHttpUrl(env), source: "env" };
|
|
48
|
+
const cfg = loadConfig();
|
|
49
|
+
if (cfg.relay && cfg.relay.length > 0)
|
|
50
|
+
return { url: toHttpUrl(cfg.relay), source: "config" };
|
|
51
|
+
return { url: null, source: "unset" };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Strict validator for **user-provided** relay URLs (via `/unbien
|
|
55
|
+
* set-relay` or `/unbien relay url`).
|
|
56
|
+
*
|
|
57
|
+
* Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
|
|
58
|
+
* **rejected** — the canonical form stored in config is http(s):// and the
|
|
59
|
+
* extension converts to ws(s):// internally when opening the WebSocket.
|
|
60
|
+
* Forcing a single scheme at the user boundary avoids two-form drift.
|
|
61
|
+
*/
|
|
62
|
+
export function isValidRelayUrl(url) {
|
|
63
|
+
if (!url)
|
|
64
|
+
return false;
|
|
65
|
+
const lower = url.toLowerCase();
|
|
66
|
+
if (!lower.startsWith("http://") && !lower.startsWith("https://"))
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
new URL(url);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns true if the URL uses ws:// or wss:// scheme — for emitting a
|
|
78
|
+
* targeted error message when the user pastes a WebSocket URL by mistake.
|
|
79
|
+
*/
|
|
80
|
+
export function isWebSocketScheme(url) {
|
|
81
|
+
const lower = url.toLowerCase();
|
|
82
|
+
return lower.startsWith("ws://") || lower.startsWith("wss://");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Converts an http(s):// URL to the corresponding ws(s):// form. Used by
|
|
86
|
+
* the transport layer right before opening the WebSocket — config storage
|
|
87
|
+
* and the mesh HTTP client both stay on http(s)://.
|
|
88
|
+
*
|
|
89
|
+
* https://host → wss://host
|
|
90
|
+
* http://host → ws://host
|
|
91
|
+
* ws(s)://host → pass-through (defensive — env overrides or legacy
|
|
92
|
+
* configs may still carry ws(s)://)
|
|
93
|
+
*/
|
|
94
|
+
export function toWebSocketUrl(url) {
|
|
95
|
+
const lower = url.toLowerCase();
|
|
96
|
+
if (lower.startsWith("https://"))
|
|
97
|
+
return "wss://" + url.slice("https://".length);
|
|
98
|
+
if (lower.startsWith("http://"))
|
|
99
|
+
return "ws://" + url.slice("http://".length);
|
|
100
|
+
return url;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
|
|
104
|
+
* ws(s):// values back to canonical http(s):// before returning them to
|
|
105
|
+
* the rest of the codebase.
|
|
106
|
+
*/
|
|
107
|
+
export function toHttpUrl(url) {
|
|
108
|
+
const lower = url.toLowerCase();
|
|
109
|
+
if (lower.startsWith("wss://"))
|
|
110
|
+
return "https://" + url.slice("wss://".length);
|
|
111
|
+
if (lower.startsWith("ws://"))
|
|
112
|
+
return "http://" + url.slice("ws://".length);
|
|
113
|
+
return url;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ControlReplyFor, type ControlRequest } from "./control_protocol.js";
|
|
2
|
+
export declare class SupervisorOfflineError extends Error {
|
|
3
|
+
readonly sockPath: string;
|
|
4
|
+
constructor(sockPath: string);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Sends a single request and returns the typed reply data.
|
|
8
|
+
*
|
|
9
|
+
* Throws:
|
|
10
|
+
* - `SupervisorOfflineError` when the supervisor isn't reachable.
|
|
11
|
+
* - `Error` from `parseReply` when the reply line is malformed.
|
|
12
|
+
* - The supervisor's own error string when `ok: false`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function callSupervisor<Op extends ControlRequest["op"]>(req: Extract<ControlRequest, {
|
|
15
|
+
op: Op;
|
|
16
|
+
}>): Promise<ControlReplyFor<Op>>;
|
|
17
|
+
/** Returns true when the supervisor is reachable. Used by `/unbien
|
|
18
|
+
* daemons` to decide whether to query runtime state or fall back to
|
|
19
|
+
* registry-only listing. */
|
|
20
|
+
export declare function supervisorOnline(): Promise<boolean>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import { encodeRequest, parseReply, } from "./control_protocol.js";
|
|
4
|
+
import { getSupervisorSockPath } from "./supervisor.js";
|
|
5
|
+
import { usesNamedPipe } from "../session/ipc.js";
|
|
6
|
+
/**
|
|
7
|
+
* Tiny client for the `un-bien` CLI to call the supervisor over the
|
|
8
|
+
* `~/.pi/un-bien/supervisor.sock` UDS.
|
|
9
|
+
*
|
|
10
|
+
* Each call opens a fresh connection, sends one request line, reads one
|
|
11
|
+
* reply line, closes. No connection pooling — the CLI is short-lived,
|
|
12
|
+
* latency is dominated by the socket round-trip (<1ms).
|
|
13
|
+
*
|
|
14
|
+
* `SupervisorOfflineError` is the common error: thrown when the socket
|
|
15
|
+
* file is missing OR the connect fails (no listener). The CLI handler
|
|
16
|
+
* formats it as a friendly "Run `unbien install` first" message.
|
|
17
|
+
*/
|
|
18
|
+
const CONNECT_TIMEOUT_MS = 1000;
|
|
19
|
+
const REPLY_TIMEOUT_MS = 5000;
|
|
20
|
+
export class SupervisorOfflineError extends Error {
|
|
21
|
+
sockPath;
|
|
22
|
+
constructor(sockPath) {
|
|
23
|
+
super(`Supervisor is not running. UDS not responding at ${sockPath}.\n` +
|
|
24
|
+
"Run `unbien install` to set it up, or start it manually with `pi-supervisord`.");
|
|
25
|
+
this.sockPath = sockPath;
|
|
26
|
+
this.name = "SupervisorOfflineError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sends a single request and returns the typed reply data.
|
|
31
|
+
*
|
|
32
|
+
* Throws:
|
|
33
|
+
* - `SupervisorOfflineError` when the supervisor isn't reachable.
|
|
34
|
+
* - `Error` from `parseReply` when the reply line is malformed.
|
|
35
|
+
* - The supervisor's own error string when `ok: false`.
|
|
36
|
+
*/
|
|
37
|
+
export async function callSupervisor(req) {
|
|
38
|
+
const sockPath = getSupervisorSockPath();
|
|
39
|
+
// POSIX fast-fail on a missing socket file. Windows pipes have no file —
|
|
40
|
+
// skip the check and let `_connect` fail fast if the pipe isn't there.
|
|
41
|
+
if (!usesNamedPipe() && !existsSync(sockPath))
|
|
42
|
+
throw new SupervisorOfflineError(sockPath);
|
|
43
|
+
const sock = await _connect(sockPath);
|
|
44
|
+
try {
|
|
45
|
+
sock.write(encodeRequest(req));
|
|
46
|
+
const line = await _readLine(sock);
|
|
47
|
+
const reply = parseReply(line);
|
|
48
|
+
if (!reply.ok)
|
|
49
|
+
throw new Error(reply.error);
|
|
50
|
+
return reply.data;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
sock.destroy();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Returns true when the supervisor is reachable. Used by `/unbien
|
|
57
|
+
* daemons` to decide whether to query runtime state or fall back to
|
|
58
|
+
* registry-only listing. */
|
|
59
|
+
export async function supervisorOnline() {
|
|
60
|
+
const sockPath = getSupervisorSockPath();
|
|
61
|
+
if (!usesNamedPipe() && !existsSync(sockPath))
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
const sock = await _connect(sockPath);
|
|
65
|
+
sock.destroy();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ── internals ───────────────────────────────────────────────────────────────
|
|
73
|
+
function _connect(sockPath) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const sock = createConnection({ path: sockPath });
|
|
76
|
+
let settled = false;
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
if (settled)
|
|
79
|
+
return;
|
|
80
|
+
settled = true;
|
|
81
|
+
sock.destroy();
|
|
82
|
+
reject(new SupervisorOfflineError(sockPath));
|
|
83
|
+
}, CONNECT_TIMEOUT_MS);
|
|
84
|
+
sock.once("connect", () => {
|
|
85
|
+
if (settled)
|
|
86
|
+
return;
|
|
87
|
+
settled = true;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
sock.setEncoding("utf8");
|
|
90
|
+
resolve(sock);
|
|
91
|
+
});
|
|
92
|
+
sock.once("error", () => {
|
|
93
|
+
if (settled)
|
|
94
|
+
return;
|
|
95
|
+
settled = true;
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
reject(new SupervisorOfflineError(sockPath));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function _readLine(sock) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
let buf = "";
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
reject(new Error("supervisor reply timeout"));
|
|
106
|
+
sock.destroy();
|
|
107
|
+
}, REPLY_TIMEOUT_MS);
|
|
108
|
+
sock.on("data", (chunk) => {
|
|
109
|
+
buf += chunk;
|
|
110
|
+
const nl = buf.indexOf("\n");
|
|
111
|
+
if (nl >= 0) {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
resolve(buf.slice(0, nl));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
sock.on("end", () => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
const nl = buf.indexOf("\n");
|
|
119
|
+
if (nl >= 0)
|
|
120
|
+
return resolve(buf.slice(0, nl));
|
|
121
|
+
if (buf.length > 0)
|
|
122
|
+
return resolve(buf);
|
|
123
|
+
reject(new Error("supervisor closed connection without replying"));
|
|
124
|
+
});
|
|
125
|
+
sock.on("error", (err) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
reject(err);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=client.js.map
|