totemlayer 0.1.97 → 0.1.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/bin/automatic-runtime.mjs +2 -1
- package/bin/codex-mission-bridge.mjs +6 -2
- package/bin/configured-native-installation.mjs +57 -0
- package/bin/connected-agent-service.mjs +5 -1
- package/bin/device-login.mjs +3 -1
- package/bin/inbox-queue.mjs +8 -5
- package/bin/mcp.mjs +18 -5
- package/bin/mission-agent-service.mjs +13 -4
- package/bin/native/darwin-lifetime +0 -0
- package/bin/native/darwin-lifetime.json +1 -1
- package/bin/native-claude-login.mjs +131 -0
- package/bin/native-claude-receiver.mjs +272 -0
- package/bin/native-cli-process.py +105 -0
- package/bin/native-harness-environment.mjs +3 -1
- package/bin/native-hermes-bridge.mjs +99 -0
- package/bin/native-hermes-installation.mjs +9 -0
- package/bin/native-hermes-mcp.mjs +107 -0
- package/bin/native-hermes-mcp.py +205 -0
- package/bin/native-hermes-metadata.py +40 -0
- package/bin/native-hermes-process.py +62 -0
- package/bin/native-hermes-receiver.mjs +28 -0
- package/bin/native-inbox-installation.mjs +144 -0
- package/bin/native-inbox-receiver.mjs +133 -0
- package/bin/native-kilo-installation.mjs +4 -0
- package/bin/native-kilo-receiver.mjs +1 -0
- package/bin/native-mcp-source.mjs +244 -0
- package/bin/native-opencode-bridge.mjs +108 -0
- package/bin/native-opencode-installation.mjs +15 -0
- package/bin/native-opencode-mcp.mjs +112 -0
- package/bin/native-opencode-receiver.mjs +22 -0
- package/bin/native-pi-bridge.mjs +170 -0
- package/bin/native-pi-installation.mjs +9 -0
- package/bin/native-pi-mcp.mjs +139 -0
- package/bin/native-pi-receiver.mjs +28 -0
- package/bin/network-retry.mjs +9 -1
- package/bin/runtime-installation.mjs +6 -1
- package/bin/sdk/errors.d.ts +1 -1
- package/bin/sdk/errors.js +1 -1
- package/bin/sdk/listener.d.ts +1 -0
- package/bin/sdk/listener.js +1 -1
- package/bin/sdk/transport.js +1 -1
- package/bin/setup-assets/api-manifest.json +1 -1
- package/bin/setup-assets/manifest.json +1 -1
- package/bin/setup-assets/setup.backup.md +2 -2
- package/bin/setup-assets/setup.md +2 -2
- package/bin/startup-diagnostics.mjs +19 -2
- package/bin/strict-installation.mjs +29 -3
- package/bin/supervisor-watchdog.mjs +44 -0
- package/bin/windows-private-path.mjs +227 -32
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { GUIDE_UNAVAILABLE, runStrictSetup } from "./strict-setup.mjs";
|
|
3
3
|
import { readReleaseSetupGuide } from "./setup-guide.mjs";
|
|
4
|
+
import { NATIVE_INSTALL_PROVIDERS } from "./configured-native-installation.mjs";
|
|
4
5
|
|
|
5
6
|
export const STRICT_INSTALL_HELP = `Install Totem with every required verification
|
|
6
7
|
|
|
@@ -9,6 +10,15 @@ export const STRICT_INSTALL_HELP = `Install Totem with every required verificati
|
|
|
9
10
|
[--device-login --no-browser]
|
|
10
11
|
totem install --read-guide
|
|
11
12
|
totem install --status
|
|
13
|
+
totem install --provider pi|hermes|opencode|kilo|claude --native-config /private/binding.json
|
|
14
|
+
--session CURRENT_NATIVE_SESSION --model CURRENT_MODEL [--status] [--json]
|
|
15
|
+
totem install --provider claude --native-config /private/binding.json
|
|
16
|
+
--session CURRENT_NATIVE_SESSION --model CURRENT_MODEL --agent-id UUID --environment PRODUCTION|SANDBOX
|
|
17
|
+
--device-login [--no-browser] [--wait-seconds 1200] [--json]
|
|
18
|
+
|
|
19
|
+
Native profiles use their existing sign-in and a private owner-prepared binding.
|
|
20
|
+
This registers/resumes the exact native receiver; it reports incomplete until live
|
|
21
|
+
receiving, restart, peer and website-owner acceptance are separately verified.
|
|
12
22
|
|
|
13
23
|
Run inside the intended agent's command-capable host. The current native session and
|
|
14
24
|
model must be verified; an installed client is never selected as a substitute.
|
|
@@ -37,6 +47,7 @@ export async function strictInstallationMain(args = [], options = {}) {
|
|
|
37
47
|
environment: { type: "string" },
|
|
38
48
|
"base-url": { type: "string" },
|
|
39
49
|
session: { type: "string" },
|
|
50
|
+
"native-config": { type: "string" },
|
|
40
51
|
model: { type: "string" },
|
|
41
52
|
"wait-seconds": { type: "string", default: "1200" },
|
|
42
53
|
"read-guide": { type: "boolean" },
|
|
@@ -94,8 +105,12 @@ export async function strictInstallationMain(args = [], options = {}) {
|
|
|
94
105
|
/tn_(?:live|test)_/.test(values.model))
|
|
95
106
|
)
|
|
96
107
|
throw new Error();
|
|
97
|
-
|
|
108
|
+
const sessionPattern = values.provider === "hermes" ? /^\d{8}_\d{6}_[a-f0-9]{6}$/
|
|
109
|
+
: ["opencode", "kilo"].includes(values.provider) ? /^ses_[A-Za-z0-9]{12,80}$/
|
|
110
|
+
: /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i;
|
|
111
|
+
if (values.session && !sessionPattern.test(values.session))
|
|
98
112
|
throw new Error();
|
|
113
|
+
if (values["native-config"] && !NATIVE_INSTALL_PROVIDERS.includes(values.provider)) throw new Error();
|
|
99
114
|
const env = options.env ?? process.env;
|
|
100
115
|
flags = {
|
|
101
116
|
provider: values.provider ?? (env.CODEX_THREAD_ID ? "codex" : undefined),
|
|
@@ -106,10 +121,12 @@ export async function strictInstallationMain(args = [], options = {}) {
|
|
|
106
121
|
model: values.model,
|
|
107
122
|
waitSeconds,
|
|
108
123
|
json: values.json === true,
|
|
124
|
+
...(values["native-config"] ? { nativeConfig: values["native-config"] } : {}),
|
|
109
125
|
};
|
|
110
126
|
if (values["device-login"]) flags.deviceLogin = true;
|
|
111
127
|
if (values["no-browser"]) flags.noBrowser = true;
|
|
112
|
-
if (values.status)
|
|
128
|
+
if (values.status && flags.nativeConfig) flags.status = true;
|
|
129
|
+
else if (values.status) {
|
|
113
130
|
flags.sessionId ??= env.CODEX_THREAD_ID;
|
|
114
131
|
const { readStrictSetupJob } = await import("./strict-setup-jobs.mjs");
|
|
115
132
|
const publicStatus = { ...(await readStrictSetupJob(flags, env)) };
|
|
@@ -130,6 +147,15 @@ export async function strictInstallationMain(args = [], options = {}) {
|
|
|
130
147
|
return 2;
|
|
131
148
|
}
|
|
132
149
|
const write = async (value) => stdout.write(JSON.stringify(value) + "\n");
|
|
150
|
+
if (flags.nativeConfig) {
|
|
151
|
+
try {
|
|
152
|
+
const { installConfiguredNativeReceiver } = await import("./configured-native-installation.mjs");
|
|
153
|
+
await write({ ...await installConfiguredNativeReceiver(flags, { ...options, write }), complete: false });
|
|
154
|
+
} catch (error) {
|
|
155
|
+
await write({ complete: false, state: "incomplete", code: /^[A-Z][A-Z0-9_]{0,79}$/.test(error.code ?? "") ? error.code : "NATIVE_INSTALL_FAILED" });
|
|
156
|
+
}
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
133
159
|
if (!options.adapter && !options.foreground && flags.provider === "codex") {
|
|
134
160
|
// Return before readiness needs a model turn in this same native chat.
|
|
135
161
|
const adapter = (await import("./strict-codex-adapter.mjs")).createStrictCodexAdapter(flags, {
|
|
@@ -190,7 +216,7 @@ export async function strictInstallationMain(args = [], options = {}) {
|
|
|
190
216
|
readGuide: readReleaseSetupGuide,
|
|
191
217
|
async inspect() {
|
|
192
218
|
throw Object.assign(new Error(), {
|
|
193
|
-
code: flags.provider ? "HOST_ADAPTER_UNSUPPORTED" : "EXACT_NATIVE_HOST_REQUIRED",
|
|
219
|
+
code: ["pi", "hermes", "opencode", "kilo"].includes(flags.provider) ? "NATIVE_CONFIGURATION_REQUIRED" : flags.provider ? "HOST_ADAPTER_UNSUPPORTED" : "EXACT_NATIVE_HOST_REQUIRED",
|
|
194
220
|
});
|
|
195
221
|
},
|
|
196
222
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { privateRead, privateWrite } from "./runtime-installation.mjs";
|
|
3
|
+
|
|
4
|
+
/** The launcher owns this ChildProcess; never discover or signal a saved PID here. */
|
|
5
|
+
export function watchSupervisorChild(child, directory, options = {}) {
|
|
6
|
+
const heartbeatFile = options.heartbeatFile ?? "supervisor.json";
|
|
7
|
+
if (!["supervisor.json", "status.json"].includes(heartbeatFile)) throw new Error("Invalid supervisor heartbeat file.");
|
|
8
|
+
const timeoutMs = options.timeoutMs ?? 300000;
|
|
9
|
+
const started = Date.now();
|
|
10
|
+
let checking = false, stopped = false, terminating = false, force;
|
|
11
|
+
const alive = () => child.exitCode === null && child.signalCode === null;
|
|
12
|
+
const stop = () => {
|
|
13
|
+
stopped = true;
|
|
14
|
+
clearInterval(timer);
|
|
15
|
+
clearTimeout(force);
|
|
16
|
+
child.removeListener("exit", stop);
|
|
17
|
+
child.removeListener("error", stop);
|
|
18
|
+
};
|
|
19
|
+
const timer = setInterval(async () => {
|
|
20
|
+
if (stopped || checking || terminating || !alive()) return;
|
|
21
|
+
checking = true;
|
|
22
|
+
try {
|
|
23
|
+
const state = await privateRead(join(directory, heartbeatFile)).catch(() => null);
|
|
24
|
+
if (stopped || !alive()) return;
|
|
25
|
+
const now = Date.now(), updated = Date.parse(state?.updatedAt);
|
|
26
|
+
// A deliberately paused dedicated inbox is quiet by design. The stored
|
|
27
|
+
// PID must still match the child this launcher owns, never another job.
|
|
28
|
+
if (options.allowPaused === true && state?.pid === child.pid && state.state === "paused") return;
|
|
29
|
+
if (now - started < timeoutMs ||
|
|
30
|
+
(state?.pid === child.pid && Number.isFinite(updated) && updated <= now + 5000 && now - updated < timeoutMs)) return;
|
|
31
|
+
terminating = true;
|
|
32
|
+
void privateWrite(join(directory, "supervisor-watchdog.json"), {
|
|
33
|
+
code: "SUPERVISOR_HEARTBEAT_TIMEOUT", pid: child.pid, updatedAt: new Date(now).toISOString(),
|
|
34
|
+
}).catch(() => {});
|
|
35
|
+
child.kill("SIGTERM");
|
|
36
|
+
force = setTimeout(() => { if (alive()) child.kill("SIGKILL"); }, options.graceMs ?? 5000);
|
|
37
|
+
force.unref();
|
|
38
|
+
} finally { checking = false; }
|
|
39
|
+
}, options.intervalMs ?? 15000);
|
|
40
|
+
timer.unref();
|
|
41
|
+
child.once("exit", stop);
|
|
42
|
+
child.once("error", stop);
|
|
43
|
+
return stop;
|
|
44
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
4
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
5
|
import { join, resolve } from "node:path";
|
|
4
6
|
import { lstat } from "node:fs/promises";
|
|
5
7
|
|
|
6
|
-
const execute = promisify(execFile);
|
|
7
8
|
export function validateWindowsPrivateAcl(snapshot) {
|
|
8
9
|
const sid = snapshot?.sid;
|
|
9
10
|
if (typeof sid !== "string" || !/^S-1-(?:\d+-){1,14}\d+$/.test(sid))
|
|
@@ -54,36 +55,230 @@ export function validateWindowsPrivateAcl(snapshot) {
|
|
|
54
55
|
throw new Error("The private Windows path or its parent can be replaced by another principal.");
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
// Keep the native process, never its results. Every request reads the current
|
|
59
|
+
// file and all ancestor ACLs again, including after a permission change.
|
|
60
|
+
const inspectionScript = (pipeName) => `$ErrorActionPreference='Stop'
|
|
61
|
+
[Console]::Error.WriteLine('TOTEM_ACL_START')
|
|
62
|
+
function Rules($acl) { @($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]) | ForEach-Object { @{sid=$_.IdentityReference.Value;rights=[int64]$_.FileSystemRights;type=$_.AccessControlType.ToString();inheritOnly=[bool]($_.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly)} }) }
|
|
63
|
+
$security=[IO.Pipes.PipeSecurity]::new()
|
|
64
|
+
$owner=[Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
65
|
+
$security.SetOwner($owner)
|
|
66
|
+
$security.SetAccessRuleProtection($true,$false)
|
|
67
|
+
$security.AddAccessRule([IO.Pipes.PipeAccessRule]::new($owner,[IO.Pipes.PipeAccessRights]::FullControl,[Security.AccessControl.AccessControlType]::Allow))
|
|
68
|
+
$pipe=[IO.Pipes.NamedPipeServerStream]::new('${pipeName}',[IO.Pipes.PipeDirection]::InOut,1,[IO.Pipes.PipeTransmissionMode]::Byte,[IO.Pipes.PipeOptions]::None,4096,4096,$security)
|
|
69
|
+
[Console]::Error.WriteLine('TOTEM_ACL_PIPE_READY')
|
|
70
|
+
try {
|
|
71
|
+
$pipe.WaitForConnection()
|
|
72
|
+
[Console]::Error.WriteLine('TOTEM_ACL_CONNECTED')
|
|
73
|
+
$hello=[Text.Encoding]::UTF8.GetBytes('{"id":0,"nonce":"'+$env:TOTEM_ACL_CHALLENGE+'"}'+"\`n")
|
|
74
|
+
$pipe.Write($hello,0,$hello.Length)
|
|
75
|
+
[Console]::Error.WriteLine('TOTEM_ACL_HELLO_SENT')
|
|
76
|
+
[byte[]]$buffer=[Array]::CreateInstance([byte],4096)
|
|
77
|
+
[Console]::Error.WriteLine('TOTEM_ACL_BUFFER_READY')
|
|
78
|
+
$pending=''
|
|
79
|
+
function Send-Frame($frame) {
|
|
80
|
+
$bytes=[Text.Encoding]::UTF8.GetBytes(($frame | ConvertTo-Json -Depth 9 -Compress)+"\`n")
|
|
81
|
+
$pipe.Write($bytes,0,$bytes.Length)
|
|
82
|
+
}
|
|
83
|
+
while ($true) {
|
|
84
|
+
while (($end=$pending.IndexOf("\`n")) -lt 0) {
|
|
85
|
+
[Console]::Error.WriteLine('TOTEM_ACL_READING')
|
|
86
|
+
$length=$pipe.Read($buffer,0,$buffer.Length)
|
|
87
|
+
if ($length -eq 0) { return }
|
|
88
|
+
[Console]::Error.WriteLine('TOTEM_ACL_FRAME_READ')
|
|
89
|
+
$pending += [Text.Encoding]::ASCII.GetString($buffer,0,$length)
|
|
90
|
+
if ($pending.Length -gt 65536) { throw 'ACL request too large' }
|
|
91
|
+
}
|
|
92
|
+
$line=$pending.Substring(0,$end)
|
|
93
|
+
$pending=$pending.Substring($end+1)
|
|
94
|
+
[Console]::Error.WriteLine('TOTEM_ACL_REQUEST_RECEIVED')
|
|
95
|
+
$request=$null
|
|
96
|
+
try {
|
|
97
|
+
$request=$line | ConvertFrom-Json
|
|
98
|
+
$path=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($request.path))
|
|
99
|
+
$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
100
|
+
$item=Get-Item -LiteralPath $path -Force
|
|
101
|
+
$acl=Get-Acl -LiteralPath $path
|
|
102
|
+
$parents=@(); $parent=Split-Path -LiteralPath $path
|
|
103
|
+
while ($parent) {
|
|
104
|
+
$p=Get-Item -LiteralPath $parent -Force
|
|
105
|
+
$parentAcl=Get-Acl -LiteralPath $parent
|
|
106
|
+
$parents+=@{owner=$parentAcl.GetOwner([Security.Principal.SecurityIdentifier]).Value;reparse=[bool]($p.Attributes -band [IO.FileAttributes]::ReparsePoint);rules=@(Rules $parentAcl)}
|
|
107
|
+
$next=Split-Path -LiteralPath $parent
|
|
108
|
+
if ($next -eq $parent) { break }; $parent=$next
|
|
109
|
+
}
|
|
110
|
+
$snapshot=@{sid=$sid;owner=$acl.GetOwner([Security.Principal.SecurityIdentifier]).Value;reparse=[bool]($item.Attributes -band [IO.FileAttributes]::ReparsePoint);rules=@(Rules $acl);ancestors=$parents}
|
|
111
|
+
Send-Frame @{id=$request.id;snapshot=$snapshot}
|
|
112
|
+
[Console]::Error.WriteLine('TOTEM_ACL_RESULT_SENT')
|
|
113
|
+
} catch {
|
|
114
|
+
Send-Frame @{id=$request.id;error='ACL inspection failed'}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} finally { $pipe.Dispose() }
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
export function createWindowsAclReader(options = {}) {
|
|
121
|
+
let worker,
|
|
122
|
+
nextId = 0;
|
|
123
|
+
const unavailable = () => new Error("The Windows access rules could not be verified.");
|
|
124
|
+
function stop(current) {
|
|
125
|
+
if (current.stopped) return;
|
|
126
|
+
current.stopped = true;
|
|
127
|
+
if (worker === current) worker = undefined;
|
|
128
|
+
clearTimeout(current.idle);
|
|
129
|
+
clearTimeout(current.reconnect);
|
|
130
|
+
for (const { timer, reject } of current.pending.values()) {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
reject(unavailable());
|
|
133
|
+
}
|
|
134
|
+
current.pending.clear();
|
|
135
|
+
current.socket?.destroy();
|
|
136
|
+
current.child?.kill();
|
|
137
|
+
}
|
|
138
|
+
function start() {
|
|
139
|
+
const environment = options.environment ?? process.env;
|
|
140
|
+
const systemRoot = options.systemRoot ?? environment.SystemRoot ?? environment.SYSTEMROOT;
|
|
141
|
+
if (!systemRoot) throw new Error("The Windows system directory could not be verified.");
|
|
142
|
+
const name = `totem-acl-${process.pid}-${randomUUID()}`;
|
|
143
|
+
const nonce = randomUUID();
|
|
144
|
+
const current = {
|
|
145
|
+
pending: new Map(),
|
|
146
|
+
buffer: "",
|
|
147
|
+
decoder: new StringDecoder("utf8"),
|
|
148
|
+
nonce,
|
|
149
|
+
stopped: false,
|
|
150
|
+
authenticated: false,
|
|
151
|
+
};
|
|
152
|
+
worker = current;
|
|
153
|
+
const child = (options.spawn ?? spawn)(
|
|
154
|
+
join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
155
|
+
[
|
|
156
|
+
"-NoProfile",
|
|
157
|
+
"-NonInteractive",
|
|
158
|
+
"-EncodedCommand",
|
|
159
|
+
Buffer.from(inspectionScript(name), "utf16le").toString("base64"),
|
|
160
|
+
],
|
|
161
|
+
{
|
|
162
|
+
// The encoded command uses a protected native pipe independently of stdin;
|
|
163
|
+
// no file paths or secrets are written to stdio.
|
|
164
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
165
|
+
windowsHide: true,
|
|
166
|
+
env: {
|
|
167
|
+
...Object.fromEntries(
|
|
168
|
+
Object.entries(environment).filter(([name]) =>
|
|
169
|
+
/^(PATH|PATHEXT|COMSPEC|TEMP|TMP|USERPROFILE|HOMEDRIVE|HOMEPATH|APPDATA|LOCALAPPDATA|PROGRAMDATA|PROGRAMFILES|PROGRAMFILES\(X86\)|PSMODULEPATH|SYSTEMDRIVE|USERNAME|USERDOMAIN)$/i.test(
|
|
170
|
+
name,
|
|
171
|
+
),
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
SystemRoot: systemRoot,
|
|
175
|
+
windir: systemRoot,
|
|
176
|
+
TOTEM_ACL_CHALLENGE: nonce,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
current.child = child;
|
|
181
|
+
child.unref();
|
|
182
|
+
child.stderr?.unref?.();
|
|
183
|
+
let diagnostics = "";
|
|
184
|
+
child.stderr?.on("data", (chunk) => {
|
|
185
|
+
diagnostics = (diagnostics + chunk.toString()).slice(-4096);
|
|
186
|
+
for (const line of diagnostics.split(/\r?\n/).slice(0, -1))
|
|
187
|
+
if (/^TOTEM_ACL_[A-Z_]+$/.test(line)) options.onDiagnostic?.(line);
|
|
188
|
+
diagnostics = diagnostics.slice(diagnostics.lastIndexOf("\n") + 1);
|
|
189
|
+
});
|
|
190
|
+
child.on("error", () => stop(current));
|
|
191
|
+
child.on("close", () => stop(current));
|
|
192
|
+
const connect = () => {
|
|
193
|
+
if (current.stopped) return;
|
|
194
|
+
const socket = (options.createConnection ?? createConnection)({
|
|
195
|
+
path: "\\\\.\\pipe\\" + name,
|
|
196
|
+
});
|
|
197
|
+
current.socket = socket;
|
|
198
|
+
// Pending request timers keep the owner alive. Owner exit closes the pipe
|
|
199
|
+
// and the native reader exits; idle inspectors cannot prolong a CLI run.
|
|
200
|
+
socket.unref();
|
|
201
|
+
let retry = false;
|
|
202
|
+
socket.on("error", (error) => {
|
|
203
|
+
if (/^[A-Z_]{1,40}$/.test(error.code ?? "")) options.onDiagnostic?.(error.code);
|
|
204
|
+
if (!current.authenticated && error.code === "ENOENT") {
|
|
205
|
+
retry = true;
|
|
206
|
+
current.reconnect = setTimeout(connect, 25);
|
|
207
|
+
current.reconnect.unref();
|
|
208
|
+
} else stop(current);
|
|
209
|
+
});
|
|
210
|
+
socket.on("close", () => {
|
|
211
|
+
if (!retry) stop(current);
|
|
212
|
+
});
|
|
213
|
+
socket.on("data", (chunk) => {
|
|
214
|
+
if (current.stopped) return;
|
|
215
|
+
current.buffer += current.decoder.write(chunk);
|
|
216
|
+
if (Buffer.byteLength(current.buffer) > 262144) return stop(current);
|
|
217
|
+
let end;
|
|
218
|
+
while ((end = current.buffer.indexOf("\n")) >= 0) {
|
|
219
|
+
const line = current.buffer.slice(0, end).replace(/^\uFEFF/, "");
|
|
220
|
+
current.buffer = current.buffer.slice(end + 1);
|
|
221
|
+
let response;
|
|
222
|
+
try {
|
|
223
|
+
response = JSON.parse(line);
|
|
224
|
+
} catch {
|
|
225
|
+
return stop(current);
|
|
226
|
+
}
|
|
227
|
+
if (!current.authenticated) {
|
|
228
|
+
if (
|
|
229
|
+
response.id !== 0 ||
|
|
230
|
+
typeof response.nonce !== "string" ||
|
|
231
|
+
response.nonce.length !== nonce.length ||
|
|
232
|
+
!/^[a-f0-9-]{36}$/.test(response.nonce) ||
|
|
233
|
+
!timingSafeEqual(Buffer.from(response.nonce), Buffer.from(nonce))
|
|
234
|
+
)
|
|
235
|
+
return stop(current);
|
|
236
|
+
current.authenticated = true;
|
|
237
|
+
options.onDiagnostic?.("AUTHENTICATED");
|
|
238
|
+
for (const pending of current.pending.values()) socket.write(pending.request);
|
|
239
|
+
options.onDiagnostic?.("REQUEST_SENT");
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const pending = current.pending.get(response.id);
|
|
243
|
+
if (!pending) return stop(current);
|
|
244
|
+
clearTimeout(pending.timer);
|
|
245
|
+
current.pending.delete(response.id);
|
|
246
|
+
options.onDiagnostic?.("RESULT_RECEIVED");
|
|
247
|
+
if (response.error || !response.snapshot) pending.reject(unavailable());
|
|
248
|
+
else pending.resolve(response.snapshot);
|
|
249
|
+
}
|
|
250
|
+
if (!current.pending.size) {
|
|
251
|
+
current.idle = setTimeout(() => stop(current), options.idleMs ?? 30000);
|
|
252
|
+
current.idle.unref();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
connect();
|
|
257
|
+
return current;
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
read(path) {
|
|
261
|
+
const encodedPath = Buffer.from(resolve(path), "utf8").toString("base64");
|
|
262
|
+
const current = worker ?? start();
|
|
263
|
+
clearTimeout(current.idle);
|
|
264
|
+
if (current.pending.size >= 1024) return Promise.reject(unavailable());
|
|
265
|
+
const id = ++nextId;
|
|
266
|
+
return new Promise((resolveRequest, reject) => {
|
|
267
|
+
const timer = setTimeout(() => stop(current), options.timeoutMs ?? 10000);
|
|
268
|
+
const request = JSON.stringify({ id, path: encodedPath }) + "\n";
|
|
269
|
+
current.pending.set(id, { resolve: resolveRequest, reject, timer, request });
|
|
270
|
+
if (current.authenticated) current.socket.write(request);
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
close() {
|
|
274
|
+
if (worker) stop(worker);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const reader = createWindowsAclReader();
|
|
57
280
|
export async function assertWindowsPrivatePath(path) {
|
|
58
281
|
if (process.platform !== "win32") return;
|
|
59
282
|
await lstat(path); // Preserve ENOENT for callers that create a new private file.
|
|
60
|
-
|
|
61
|
-
const script = `$ErrorActionPreference='Stop'
|
|
62
|
-
$path=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encoded}'))
|
|
63
|
-
$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
64
|
-
function Rules($acl) { @($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]) | ForEach-Object { @{sid=$_.IdentityReference.Value;rights=[int64]$_.FileSystemRights;type=$_.AccessControlType.ToString();inheritOnly=[bool]($_.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly)} }) }
|
|
65
|
-
$item=Get-Item -LiteralPath $path -Force
|
|
66
|
-
$acl=Get-Acl -LiteralPath $path
|
|
67
|
-
$parents=@(); $parent=Split-Path -LiteralPath $path
|
|
68
|
-
while ($parent) {
|
|
69
|
-
$p=Get-Item -LiteralPath $parent -Force
|
|
70
|
-
$parentAcl=Get-Acl -LiteralPath $parent
|
|
71
|
-
$parents+=@{owner=$parentAcl.GetOwner([Security.Principal.SecurityIdentifier]).Value;reparse=[bool]($p.Attributes -band [IO.FileAttributes]::ReparsePoint);rules=@(Rules $parentAcl)}
|
|
72
|
-
$next=Split-Path -LiteralPath $parent
|
|
73
|
-
if ($next -eq $parent) { break }; $parent=$next
|
|
74
|
-
}
|
|
75
|
-
@{sid=$sid;owner=$acl.GetOwner([Security.Principal.SecurityIdentifier]).Value;reparse=[bool]($item.Attributes -band [IO.FileAttributes]::ReparsePoint);rules=@(Rules $acl);ancestors=$parents} | ConvertTo-Json -Depth 8 -Compress`;
|
|
76
|
-
const systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT;
|
|
77
|
-
if (!systemRoot) throw new Error("The Windows system directory could not be verified.");
|
|
78
|
-
const { stdout } = await execute(
|
|
79
|
-
join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
80
|
-
[
|
|
81
|
-
"-NoProfile",
|
|
82
|
-
"-NonInteractive",
|
|
83
|
-
"-EncodedCommand",
|
|
84
|
-
Buffer.from(script, "utf16le").toString("base64"),
|
|
85
|
-
],
|
|
86
|
-
{ timeout: 10000, maxBuffer: 262144, windowsHide: true },
|
|
87
|
-
);
|
|
88
|
-
validateWindowsPrivateAcl(JSON.parse(stdout.replace(/^\uFEFF/, "")));
|
|
283
|
+
validateWindowsPrivateAcl(await reader.read(path));
|
|
89
284
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "totemlayer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.115",
|
|
4
4
|
"description": "Connect AI agents to Totem Network for discovery, messaging, and shared work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"homepage": "https://totemlayer.com",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/
|
|
37
|
+
"url": "git+https://github.com/linbrandon4/Totem.git"
|
|
38
38
|
},
|
|
39
39
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
40
40
|
"publishConfig": {
|