hilos-agent 0.11.10 → 0.11.12
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/package.json +1 -1
- package/src/hook.mjs +23 -16
- package/src/seat-repository.d.mts +1 -0
- package/src/seat-repository.mjs +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hilos-agent",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.12",
|
|
4
4
|
"description": "Run your own coding agent (Claude Code, Codex, Cursor, OpenCode, Hermes, or any command) as a teammate in a hilos room. The checkout and credentials stay local; changes go to your configured Git remote as a PR for human review, and bounded progress and reports go to hilos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/hook.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import { join, dirname } from "node:path";
|
|
|
26
26
|
import { fileURLToPath } from "node:url";
|
|
27
27
|
import { sanitizeText, webTarget } from "./agent-events.mjs";
|
|
28
28
|
import { resolveConfig } from "./config.mjs";
|
|
29
|
+
import { normalizeSeatRepository } from "./seat-repository.mjs";
|
|
29
30
|
|
|
30
31
|
export const HOOK_STATE_DIR = join(homedir(), ".hilos", "hook-state");
|
|
31
32
|
export const CODEX_HOOK_SCOPE_FILE = join(homedir(), ".hilos", "codex-hook-scope.json");
|
|
@@ -376,13 +377,15 @@ const PERSONAL_CLIENTS = {
|
|
|
376
377
|
};
|
|
377
378
|
|
|
378
379
|
/**
|
|
379
|
-
* Resolve the personal-seat config from the environment.
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
380
|
+
* Resolve the personal-seat config from the environment. Claude Code exports
|
|
381
|
+
* its saved plugin option to hooks; manual hooks and other clients still use
|
|
382
|
+
* HILOS_MCP_TOKEN. A nonempty saved option wins, including an invalid/revoked
|
|
383
|
+
* one: never silently attach as the fallback identity. PURE.
|
|
383
384
|
*/
|
|
384
385
|
export function personalHookConfig(env = {}, { vendor = "unknown" } = {}) {
|
|
385
|
-
const
|
|
386
|
+
const option = typeof env.CLAUDE_PLUGIN_OPTION_HILOS_MCP_TOKEN === "string"
|
|
387
|
+
? env.CLAUDE_PLUGIN_OPTION_HILOS_MCP_TOKEN.trim() : "";
|
|
388
|
+
const token = option || (typeof env.HILOS_MCP_TOKEN === "string" ? env.HILOS_MCP_TOKEN.trim() : "");
|
|
386
389
|
if (!token || !token.startsWith("hpt_")) return null;
|
|
387
390
|
const client = PERSONAL_CLIENTS[vendor];
|
|
388
391
|
if (!client) return null;
|
|
@@ -402,24 +405,25 @@ export function seatLabel(value) {
|
|
|
402
405
|
}
|
|
403
406
|
|
|
404
407
|
/**
|
|
405
|
-
* The
|
|
406
|
-
*
|
|
407
|
-
* timeout, and a failure just falls back — a hook never blocks the CLI.
|
|
408
|
+
* The canonical origin for this session. Git is asked once at attach with a
|
|
409
|
+
* short timeout. A missing or unsupported origin leaves the seat unbound.
|
|
408
410
|
*/
|
|
409
|
-
export function
|
|
410
|
-
const folder = seatLabel(cwd);
|
|
411
|
+
export function readSeatRepository(cwd, { spawn = spawnSync } = {}) {
|
|
411
412
|
try {
|
|
412
413
|
const out = spawn("git", ["-C", String(cwd || "."), "remote", "get-url", "origin"], {
|
|
413
414
|
encoding: "utf8",
|
|
414
415
|
timeout: 1500,
|
|
415
416
|
});
|
|
416
|
-
|
|
417
|
-
return remote || folder;
|
|
417
|
+
return out?.status === 0 ? normalizeSeatRepository(out.stdout) : null;
|
|
418
418
|
} catch {
|
|
419
|
-
return
|
|
419
|
+
return null;
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
export function readSeatLabel(cwd, options) {
|
|
424
|
+
return seatLabel(readSeatRepository(cwd, options)) || seatLabel(cwd);
|
|
425
|
+
}
|
|
426
|
+
|
|
423
427
|
/** One bounded personal `tools/call`. Returns the parsed payload, or null. */
|
|
424
428
|
export async function callPersonalTool({ url, token, client, name, args }) {
|
|
425
429
|
const controller = new AbortController();
|
|
@@ -463,7 +467,8 @@ export async function runPersonalSeat({
|
|
|
463
467
|
now = Date.now,
|
|
464
468
|
note = "",
|
|
465
469
|
call = callPersonalTool,
|
|
466
|
-
label
|
|
470
|
+
label,
|
|
471
|
+
repository = readSeatRepository,
|
|
467
472
|
}) {
|
|
468
473
|
if (!personal || !ev) return state;
|
|
469
474
|
const seat = state.seat && typeof state.seat === "object" ? state.seat : {};
|
|
@@ -479,12 +484,14 @@ export async function runPersonalSeat({
|
|
|
479
484
|
ev.event === "PostToolUse";
|
|
480
485
|
if (!seat.id && !wantsSeat) return state;
|
|
481
486
|
if (ev.event === "SessionStart" || !seat.id) {
|
|
487
|
+
const repositoryUrl = repository(ev.cwd);
|
|
482
488
|
const attached = await call({
|
|
483
489
|
...base,
|
|
484
490
|
name: "attach_session",
|
|
485
491
|
args: {
|
|
486
492
|
client: personal.client,
|
|
487
|
-
label: label(ev.cwd),
|
|
493
|
+
label: label ? label(ev.cwd) : seatLabel(repositoryUrl) || seatLabel(ev.cwd),
|
|
494
|
+
...(repositoryUrl ? { repositoryUrl } : {}),
|
|
488
495
|
sessionKey: ev.sessionId,
|
|
489
496
|
},
|
|
490
497
|
});
|
|
@@ -945,7 +952,7 @@ export async function hookMain({ vendor = "unknown", scopeManaged = false, perso
|
|
|
945
952
|
// `--personal` is the plugin's install: this session belongs to a person,
|
|
946
953
|
// so it never streams into an agent's live card even when an agent
|
|
947
954
|
// credential happens to sit in ~/.hilos/agent.json. The seat itself rides
|
|
948
|
-
//
|
|
955
|
+
// the saved Claude plugin option, or HILOS_MCP_TOKEN for manual hooks.
|
|
949
956
|
const cfg = personal ? {} : resolveConfig({});
|
|
950
957
|
await runHook({ raw, cfg, vendor, personal: personalHookConfig(process.env, { vendor }) });
|
|
951
958
|
} catch {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function normalizeSeatRepository(value: unknown): string | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A GitHub origin as a credential-free HTTPS URL. Room links currently name
|
|
3
|
+
* GitHub repositories, so another host (even with the same owner/repo), local
|
|
4
|
+
* paths, SSH aliases, and malformed remotes cannot select a room.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeSeatRepository(value) {
|
|
7
|
+
if (typeof value !== "string") return null;
|
|
8
|
+
const text = value.trim();
|
|
9
|
+
if (!text || /[\\\s]/.test(text)) return null;
|
|
10
|
+
let url;
|
|
11
|
+
try {
|
|
12
|
+
const scp = text.match(/^git@github\.com:([^?#]+)$/i);
|
|
13
|
+
url = new URL(scp ? `https://github.com/${scp[1]}` : text);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol) ||
|
|
18
|
+
url.hostname.toLowerCase() !== "github.com" || url.port) return null;
|
|
19
|
+
const path = url.pathname.replace(/\/$/, "").replace(/\.git$/i, "");
|
|
20
|
+
if (!/^\/[a-z0-9-]+\/[a-z0-9_.-]+$/i.test(path)) return null;
|
|
21
|
+
const [, owner, repo] = path.split("/");
|
|
22
|
+
if (repo === "." || repo === "..") return null;
|
|
23
|
+
return `https://github.com/${owner.toLowerCase()}/${repo.toLowerCase()}`;
|
|
24
|
+
}
|