@sjawhar/claude-envoy 1.12.1 → 1.14.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/.claude-plugin/plugin.json +1 -1
- package/.omp-plugin/plugin.json +1 -1
- package/dist/dispatch.js +58 -27
- package/dist/envoy-channel.js +111 -55
- package/dist/session-hook.js +53 -23
- package/legion/skills/legion-architect/SKILL.md +10 -8
- package/legion/skills/legion-architect/references/acceptance.md +1 -1
- package/legion/skills/legion-architect/references/decomposition.md +27 -18
- package/legion/skills/legion-architect/references/design-gate.md +4 -4
- package/legion/skills/legion-architect/references/wake-routing.md +5 -5
- package/legion/skills/legion-controller/SKILL.md +5 -2
- package/legion/skills/legion-controller/references/daily-report.md +7 -0
- package/legion/skills/legion-controller/references/weekly-audit.md +146 -0
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +2 -2
- package/skills/dispatch-first/SKILL.md +3 -7
- package/skills/dispatch-writing-specs/SKILL.md +1 -1
- package/src/session-title.ts +3 -42
package/.omp-plugin/plugin.json
CHANGED
package/dist/dispatch.js
CHANGED
|
@@ -14662,7 +14662,8 @@ var IssueEventPayloadSchema = object({
|
|
|
14662
14662
|
route: string2().nullish(),
|
|
14663
14663
|
changed: array(string2()).optional(),
|
|
14664
14664
|
ancestors: array(string2()).optional(),
|
|
14665
|
-
thread_logins: array(string2()).optional()
|
|
14665
|
+
thread_logins: array(string2()).optional(),
|
|
14666
|
+
forced: object({ past: array(string2()) }).optional()
|
|
14666
14667
|
});
|
|
14667
14668
|
var ArtifactCreatedEventPayloadSchema = object({
|
|
14668
14669
|
artifact: object({ id: string2().optional(), slug: string2().optional(), name: string2().optional() }).optional()
|
|
@@ -15615,12 +15616,12 @@ var dispatchToolSpecs = [
|
|
|
15615
15616
|
{
|
|
15616
15617
|
name: "dispatch_related",
|
|
15617
15618
|
example: { project: "CORE", title: "Retry failed webhook deliveries" },
|
|
15618
|
-
description: "Run the related-item check that dispatch issue, dispatch ask and a new decision block in " + "dispatch doc-edit run on their own write, and write nothing: it lists the three items of " + "one project most like a title and a text (at most one per issue or document, open ones " + "first), each ask with whether it is answered, and a past decision that already answers " + "the same question. Use it to check a draft before filing it. It searches by meaning and by " + "the text's rarest words, and says how it searched; when meaning search cannot run it " + "searches by keywords only and says why. When it could not search at all it says why and " + "names the dispatch search to run instead.
|
|
15619
|
+
description: "Run the related-item check that dispatch issue, dispatch ask and a new decision block in " + "dispatch doc-edit run on their own write, and write nothing: it lists the three items of " + "one project most like a title and a text (at most one per issue or document, open ones " + "first), each ask with whether it is answered, and a past decision that already answers " + "the same question. Use it to check a draft before filing it. It searches by meaning and by " + "the text's rarest words, and says how it searched; when meaning search cannot run it " + "searches by keywords only and says why. When it could not search at all it says why and " + "names the dispatch search to run instead. exclude_issues leaves each named issue's own " + "items out of the results, at most 50 of them, so a draft under a parent can leave out " + "that parent and its children without changing how rare the text's words are; " + "keywords_only skips meaning search.",
|
|
15619
15620
|
arguments: (z) => ({
|
|
15620
15621
|
project: z.string().describe("Project key to search within, such as CORE."),
|
|
15621
15622
|
title: z.string({ max: ISSUE_TITLE_MAX }).describe("Optional title to check, as an issue title; at most 1,000 characters.").optional(),
|
|
15622
15623
|
text: z.string().describe("Optional text to check, such as a draft spec or a question.").optional(),
|
|
15623
|
-
|
|
15624
|
+
exclude_issues: z.array(z.string()).describe("Optional issue keys in the project whose own items are left out of the results; at most 50.").optional(),
|
|
15624
15625
|
keywords_only: z.boolean().describe("Search by keywords only, skipping meaning search.").optional()
|
|
15625
15626
|
}),
|
|
15626
15627
|
validation: {
|
|
@@ -17565,9 +17566,12 @@ async function tryExec(exec, file, args, cwd, limitMs) {
|
|
|
17565
17566
|
exec(file, args, { cwd, signal: controller.signal }),
|
|
17566
17567
|
expired.promise
|
|
17567
17568
|
]);
|
|
17568
|
-
|
|
17569
|
+
if (answer === null) {
|
|
17570
|
+
return { failure: "timedOut", ok: false };
|
|
17571
|
+
}
|
|
17572
|
+
return { ok: true, stdout: answer.stdout };
|
|
17569
17573
|
} catch {
|
|
17570
|
-
return
|
|
17574
|
+
return { failure: "refused", ok: false };
|
|
17571
17575
|
} finally {
|
|
17572
17576
|
clearTimeout(timer);
|
|
17573
17577
|
}
|
|
@@ -17608,13 +17612,13 @@ function parseGitHubRemoteUrl(url) {
|
|
|
17608
17612
|
return null;
|
|
17609
17613
|
}
|
|
17610
17614
|
async function resolveCwdRepo(cwd, exec) {
|
|
17611
|
-
const
|
|
17612
|
-
if (
|
|
17613
|
-
const url = selectRemoteUrl(parseRemoteList(
|
|
17615
|
+
const remoteList = await tryExec(exec, "jj", ["git", "remote", "list"], cwd, REMOTE_LOOKUP_MS);
|
|
17616
|
+
if (remoteList.ok) {
|
|
17617
|
+
const url = selectRemoteUrl(parseRemoteList(remoteList.stdout));
|
|
17614
17618
|
return url ? parseGitHubRemoteUrl(url) : null;
|
|
17615
17619
|
}
|
|
17616
|
-
const
|
|
17617
|
-
return
|
|
17620
|
+
const originRemote = await tryExec(exec, "git", ["remote", "get-url", "origin"], cwd, REMOTE_LOOKUP_MS);
|
|
17621
|
+
return originRemote.ok ? parseGitHubRemoteUrl(originRemote.stdout) : null;
|
|
17618
17622
|
}
|
|
17619
17623
|
async function resolveOrigin(env, exec, cwd) {
|
|
17620
17624
|
const origin = {
|
|
@@ -17623,8 +17627,8 @@ async function resolveOrigin(env, exec, cwd) {
|
|
|
17623
17627
|
};
|
|
17624
17628
|
const pane = env.TMUX_PANE;
|
|
17625
17629
|
if (pane) {
|
|
17626
|
-
const
|
|
17627
|
-
const [target, paneId] =
|
|
17630
|
+
const answer = await tryExec(exec, "tmux", ["display-message", "-p", "-t", pane, "#S:#I.#P #{pane_id}"], cwd, TMUX_PANE_LOOKUP_MS);
|
|
17631
|
+
const [target, paneId] = answer.ok ? answer.stdout.trim().split(" ") : [];
|
|
17628
17632
|
if (target)
|
|
17629
17633
|
origin.tmux = target;
|
|
17630
17634
|
if (paneId)
|
|
@@ -20540,11 +20544,19 @@ function relatedCheckLines(suggestions, context) {
|
|
|
20540
20544
|
lines.push(`A past decision may already answer this: ${suggestionLabel(decision)}, answered by ${decision.answered_by}${when} \u2192 ${absoluteHref(decision.href, context.configUrl)}`);
|
|
20541
20545
|
lines.push(...askLines(decision.ask));
|
|
20542
20546
|
}
|
|
20547
|
+
const likely = suggestions.likely_duplicate;
|
|
20548
|
+
if (likely !== undefined && likely !== null) {
|
|
20549
|
+
lines.push(`Likely duplicate: ${likely.key} [${likely.status}] ${likely.title} \u2192 ${absoluteHref(likely.href, context.configUrl)}`);
|
|
20550
|
+
lines.push("Read it. If it is the same work, fold this issue into it by the duplicate rule of skill://dispatch-first.");
|
|
20551
|
+
}
|
|
20552
|
+
if (suggestions.near_copy_check !== undefined && suggestions.near_copy_check !== "") {
|
|
20553
|
+
lines.push(`No near-copy check was made: ${suggestions.near_copy_check}.`);
|
|
20554
|
+
}
|
|
20543
20555
|
for (const match of suggestions.title_matches ?? []) {
|
|
20544
|
-
lines.push(`
|
|
20556
|
+
lines.push(`Near copy you forced past: ${match.key} [${match.status}] ${match.title} \u2192 ${absoluteHref(match.href, context.configUrl)}`);
|
|
20545
20557
|
}
|
|
20546
20558
|
} catch (error) {
|
|
20547
|
-
lines.push(`The check's past decision or
|
|
20559
|
+
lines.push(`The check's past decision or near copies could not be shown: ${messageFor(error)}`);
|
|
20548
20560
|
}
|
|
20549
20561
|
return lines;
|
|
20550
20562
|
}
|
|
@@ -22427,7 +22439,13 @@ function eventHead(event) {
|
|
|
22427
22439
|
return textHead(`${event.payload.name} v${event.payload.version.number}${event.payload.version.summary ? `: ${event.payload.version.summary}` : ""}`);
|
|
22428
22440
|
case "artifact.human_edited":
|
|
22429
22441
|
return textHead(humanEditHead(event.payload));
|
|
22430
|
-
case "issue.created":
|
|
22442
|
+
case "issue.created": {
|
|
22443
|
+
const forced = event.payload.forced;
|
|
22444
|
+
const head = `status ${event.payload.status}`;
|
|
22445
|
+
if (forced === undefined)
|
|
22446
|
+
return head;
|
|
22447
|
+
return forced.past.length === 0 ? `${head} \xB7 forced, the near-copy check did not answer` : `${head} \xB7 forced past ${forced.past.join(", ")}`;
|
|
22448
|
+
}
|
|
22431
22449
|
case "issue.updated":
|
|
22432
22450
|
case "issue.closed":
|
|
22433
22451
|
return `status ${event.payload.status}`;
|
|
@@ -22443,10 +22461,14 @@ function eventRef(event) {
|
|
|
22443
22461
|
const kind = event.type.startsWith("message.") ? "message" : event.type.startsWith("comment.") || event.type.startsWith("suggestion.") ? "comment" : event.type.startsWith("ask.") ? "ask" : undefined;
|
|
22444
22462
|
return kind === undefined ? undefined : `dispatch://${event.issue_key}/${kind}/${id}`;
|
|
22445
22463
|
}
|
|
22464
|
+
function eventActorText(actor) {
|
|
22465
|
+
const title = actor.kind === "session" ? actor.origin?.session_title : undefined;
|
|
22466
|
+
return title === undefined || title === "" ? actorText(actor) : `${actorText(actor)} (${title})`;
|
|
22467
|
+
}
|
|
22446
22468
|
function eventLine(event) {
|
|
22447
22469
|
const head = eventHead(event);
|
|
22448
22470
|
const ref = eventRef(event);
|
|
22449
|
-
return `- #${event.seq} ${event.type}${ref === undefined ? "" : ` ${ref}`} \xB7 ${
|
|
22471
|
+
return `- #${event.seq} ${event.type}${ref === undefined ? "" : ` ${ref}`} \xB7 ${eventActorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
22450
22472
|
}
|
|
22451
22473
|
function childrenSummary(issue) {
|
|
22452
22474
|
return [
|
|
@@ -23263,15 +23285,17 @@ async function executeDispatchTool(input) {
|
|
|
23263
23285
|
const candidates = duplicateCandidates(error);
|
|
23264
23286
|
return {
|
|
23265
23287
|
text: [
|
|
23266
|
-
`Not created: "${title}"
|
|
23288
|
+
`Not created: "${title}" is a near copy of an open issue.`,
|
|
23267
23289
|
...candidates.map((candidate) => {
|
|
23268
23290
|
const href = new URL(candidate.href, linkBase).toString();
|
|
23269
23291
|
return `${candidate.key} [${candidate.status}] ${candidate.title} \u2192 ${href}`;
|
|
23270
23292
|
}),
|
|
23271
|
-
"
|
|
23293
|
+
"Read each issue above. If one is the same work, fold this into it by the duplicate rule of skill://dispatch-first.",
|
|
23294
|
+
"If none is, run dispatch issue again with --force, and write in the new issue's spec why it is not each issue named here."
|
|
23272
23295
|
].join(`
|
|
23273
23296
|
`),
|
|
23274
|
-
details: { duplicates: candidates }
|
|
23297
|
+
details: { duplicates: candidates },
|
|
23298
|
+
refused: true
|
|
23275
23299
|
};
|
|
23276
23300
|
}
|
|
23277
23301
|
}
|
|
@@ -23439,12 +23463,12 @@ async function executeDispatchTool(input) {
|
|
|
23439
23463
|
const project = stringArg(args, "project");
|
|
23440
23464
|
const title = optionalString(args, "title");
|
|
23441
23465
|
const text = optionalString(args, "text");
|
|
23442
|
-
const
|
|
23466
|
+
const excludeIssues = optionalStringArray(args, "exclude_issues");
|
|
23443
23467
|
const keywordsOnly = optionalBoolean(args, "keywords_only");
|
|
23444
23468
|
const suggestions = await client.related(project, {
|
|
23445
23469
|
...title === undefined ? {} : { title },
|
|
23446
23470
|
...text === undefined ? {} : { text },
|
|
23447
|
-
...
|
|
23471
|
+
...excludeIssues === undefined ? {} : { exclude_issues: excludeIssues },
|
|
23448
23472
|
...keywordsOnly === undefined ? {} : { keywords_only: keywordsOnly }
|
|
23449
23473
|
});
|
|
23450
23474
|
return {
|
|
@@ -27532,6 +27556,7 @@ function dispatchTarget(env, config) {
|
|
|
27532
27556
|
var DEFAULT_ENVOY_URL = "http://127.0.0.1:9020";
|
|
27533
27557
|
var DEFAULT_HEARTBEAT_MS = SESSION_HEARTBEAT_MS;
|
|
27534
27558
|
var MIN_PASS_DEADLINE_MS = 30000;
|
|
27559
|
+
var DEFAULT_TMUX_PLACE_CACHE_MS = 30000;
|
|
27535
27560
|
var MIN_INTERVAL_MS = 25;
|
|
27536
27561
|
var MAX_INTERVAL_MS = 2147483647;
|
|
27537
27562
|
var REGISTRATION_SETTLE_MS = 5000;
|
|
@@ -27545,6 +27570,10 @@ function namedInterval(raw) {
|
|
|
27545
27570
|
const parsed = Number(raw);
|
|
27546
27571
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
27547
27572
|
}
|
|
27573
|
+
function resolvedInterval(raw, fallback) {
|
|
27574
|
+
const named = namedInterval(raw);
|
|
27575
|
+
return named === undefined ? fallback : Math.min(Math.max(named, MIN_INTERVAL_MS), MAX_INTERVAL_MS);
|
|
27576
|
+
}
|
|
27548
27577
|
function natsSeedFromEnvironment(env) {
|
|
27549
27578
|
const { NATS_NKEY_SEED_FILE: file, NATS_NKEY_SEED: plain } = env;
|
|
27550
27579
|
if (file !== undefined) {
|
|
@@ -27635,8 +27664,8 @@ function envoySettings(env, config, options = {}) {
|
|
|
27635
27664
|
inbound = { off: messageFor(error) };
|
|
27636
27665
|
}
|
|
27637
27666
|
}
|
|
27638
|
-
const
|
|
27639
|
-
const
|
|
27667
|
+
const heartbeatMs = resolvedInterval(env.ENVOY_HEARTBEAT_MS, DEFAULT_HEARTBEAT_MS);
|
|
27668
|
+
const tmuxPlaceCacheMs = resolvedInterval(env.ENVOY_TMUX_PLACE_CACHE_MS, DEFAULT_TMUX_PLACE_CACHE_MS);
|
|
27640
27669
|
const namedPassDeadlineMs = namedInterval(env.ENVOY_PASS_DEADLINE_MS);
|
|
27641
27670
|
return {
|
|
27642
27671
|
enabled: true,
|
|
@@ -27644,6 +27673,7 @@ function envoySettings(env, config, options = {}) {
|
|
|
27644
27673
|
credential,
|
|
27645
27674
|
inbound,
|
|
27646
27675
|
heartbeatMs,
|
|
27676
|
+
tmuxPlaceCacheMs,
|
|
27647
27677
|
passDeadlineMs: Math.min(Math.max(namedPassDeadlineMs ?? MIN_PASS_DEADLINE_MS, heartbeatMs), MAX_INTERVAL_MS),
|
|
27648
27678
|
passDeadlineRaisedFrom: namedPassDeadlineMs !== undefined && namedPassDeadlineMs < heartbeatMs ? namedPassDeadlineMs : undefined
|
|
27649
27679
|
};
|
|
@@ -28063,6 +28093,7 @@ function createEnvoyClient(config) {
|
|
|
28063
28093
|
...input.delivery === undefined ? {} : { delivery: input.delivery },
|
|
28064
28094
|
...input.clientInstanceID === undefined ? {} : { client_instance_id: input.clientInstanceID },
|
|
28065
28095
|
host: machineID(),
|
|
28096
|
+
...input.place === undefined ? {} : { tmux_place: input.place },
|
|
28066
28097
|
plugin: input.plugin.name,
|
|
28067
28098
|
plugin_version: input.plugin.version
|
|
28068
28099
|
}, { actingSession: input.sessionID }))),
|
|
@@ -28283,7 +28314,7 @@ async function runDispatchCli(argv, rawEnv, io) {
|
|
|
28283
28314
|
if (parsed.kind === "refused") {
|
|
28284
28315
|
return parsed.tool === undefined ? refuse([`dispatch: ${parsed.problems.join(`
|
|
28285
28316
|
`)}`, "Run dispatch --help."].join(`
|
|
28286
|
-
`), USAGE) : refuse(new ToolInputError(parsed.tool, parsed.problems).message,
|
|
28317
|
+
`), USAGE) : refuse(new ToolInputError(parsed.tool, parsed.problems).message, USAGE, parsed.tool);
|
|
28287
28318
|
}
|
|
28288
28319
|
const hostName = env.DISPATCH_HOST ?? (env.CLAUDE_CODE_CHILD_SESSION === "1" ? "claude" : undefined);
|
|
28289
28320
|
const host = HOSTS.find((name) => name === hostName);
|
|
@@ -28311,7 +28342,7 @@ async function runDispatchCli(argv, rawEnv, io) {
|
|
|
28311
28342
|
serverUrls: settings?.enabled === true ? dispatchServerUrls(settings) : []
|
|
28312
28343
|
});
|
|
28313
28344
|
if (problems.length > 0) {
|
|
28314
|
-
return refuse(new ToolInputError(parsed.tool, problems).message,
|
|
28345
|
+
return refuse(new ToolInputError(parsed.tool, problems).message, USAGE, parsed.tool);
|
|
28315
28346
|
}
|
|
28316
28347
|
print(JSON.stringify(parsed.args, null, 2));
|
|
28317
28348
|
return OK;
|
|
@@ -28448,7 +28479,7 @@ async function runDispatchCli(argv, rawEnv, io) {
|
|
|
28448
28479
|
notes.push(followNoticeText(followed, await subscriptions()));
|
|
28449
28480
|
}
|
|
28450
28481
|
entry = { tool, details: result.details };
|
|
28451
|
-
code = OK;
|
|
28482
|
+
code = result.refused === true ? REFUSED : OK;
|
|
28452
28483
|
} catch (error) {
|
|
28453
28484
|
text = messageFor(error);
|
|
28454
28485
|
if (error instanceof DispatchWriteLandedError) {
|
|
@@ -28456,7 +28487,7 @@ async function runDispatchCli(argv, rawEnv, io) {
|
|
|
28456
28487
|
code = OK;
|
|
28457
28488
|
} else {
|
|
28458
28489
|
entry = { tool, error: text };
|
|
28459
|
-
code = REFUSED;
|
|
28490
|
+
code = error instanceof ToolInputError ? USAGE : REFUSED;
|
|
28460
28491
|
}
|
|
28461
28492
|
}
|
|
28462
28493
|
try {
|
package/dist/envoy-channel.js
CHANGED
|
@@ -36311,7 +36311,8 @@ var IssueEventPayloadSchema = object({
|
|
|
36311
36311
|
route: string2().nullish(),
|
|
36312
36312
|
changed: array(string2()).optional(),
|
|
36313
36313
|
ancestors: array(string2()).optional(),
|
|
36314
|
-
thread_logins: array(string2()).optional()
|
|
36314
|
+
thread_logins: array(string2()).optional(),
|
|
36315
|
+
forced: object({ past: array(string2()) }).optional()
|
|
36315
36316
|
});
|
|
36316
36317
|
var ArtifactCreatedEventPayloadSchema = object({
|
|
36317
36318
|
artifact: object({ id: string2().optional(), slug: string2().optional(), name: string2().optional() }).optional()
|
|
@@ -37264,12 +37265,12 @@ var dispatchToolSpecs = [
|
|
|
37264
37265
|
{
|
|
37265
37266
|
name: "dispatch_related",
|
|
37266
37267
|
example: { project: "CORE", title: "Retry failed webhook deliveries" },
|
|
37267
|
-
description: "Run the related-item check that dispatch issue, dispatch ask and a new decision block in " + "dispatch doc-edit run on their own write, and write nothing: it lists the three items of " + "one project most like a title and a text (at most one per issue or document, open ones " + "first), each ask with whether it is answered, and a past decision that already answers " + "the same question. Use it to check a draft before filing it. It searches by meaning and by " + "the text's rarest words, and says how it searched; when meaning search cannot run it " + "searches by keywords only and says why. When it could not search at all it says why and " + "names the dispatch search to run instead.
|
|
37268
|
+
description: "Run the related-item check that dispatch issue, dispatch ask and a new decision block in " + "dispatch doc-edit run on their own write, and write nothing: it lists the three items of " + "one project most like a title and a text (at most one per issue or document, open ones " + "first), each ask with whether it is answered, and a past decision that already answers " + "the same question. Use it to check a draft before filing it. It searches by meaning and by " + "the text's rarest words, and says how it searched; when meaning search cannot run it " + "searches by keywords only and says why. When it could not search at all it says why and " + "names the dispatch search to run instead. exclude_issues leaves each named issue's own " + "items out of the results, at most 50 of them, so a draft under a parent can leave out " + "that parent and its children without changing how rare the text's words are; " + "keywords_only skips meaning search.",
|
|
37268
37269
|
arguments: (z) => ({
|
|
37269
37270
|
project: z.string().describe("Project key to search within, such as CORE."),
|
|
37270
37271
|
title: z.string({ max: ISSUE_TITLE_MAX }).describe("Optional title to check, as an issue title; at most 1,000 characters.").optional(),
|
|
37271
37272
|
text: z.string().describe("Optional text to check, such as a draft spec or a question.").optional(),
|
|
37272
|
-
|
|
37273
|
+
exclude_issues: z.array(z.string()).describe("Optional issue keys in the project whose own items are left out of the results; at most 50.").optional(),
|
|
37273
37274
|
keywords_only: z.boolean().describe("Search by keywords only, skipping meaning search.").optional()
|
|
37274
37275
|
}),
|
|
37275
37276
|
validation: {
|
|
@@ -44626,6 +44627,7 @@ function fileBearer(bearer, url, section, env, commands = "run") {
|
|
|
44626
44627
|
var DEFAULT_ENVOY_URL = "http://127.0.0.1:9020";
|
|
44627
44628
|
var DEFAULT_HEARTBEAT_MS = SESSION_HEARTBEAT_MS;
|
|
44628
44629
|
var MIN_PASS_DEADLINE_MS = 30000;
|
|
44630
|
+
var DEFAULT_TMUX_PLACE_CACHE_MS = 30000;
|
|
44629
44631
|
var MIN_INTERVAL_MS = 25;
|
|
44630
44632
|
var MAX_INTERVAL_MS = 2147483647;
|
|
44631
44633
|
var REGISTRATION_SETTLE_MS = 5000;
|
|
@@ -44639,6 +44641,10 @@ function namedInterval(raw) {
|
|
|
44639
44641
|
const parsed = Number(raw);
|
|
44640
44642
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
44641
44643
|
}
|
|
44644
|
+
function resolvedInterval(raw, fallback) {
|
|
44645
|
+
const named = namedInterval(raw);
|
|
44646
|
+
return named === undefined ? fallback : Math.min(Math.max(named, MIN_INTERVAL_MS), MAX_INTERVAL_MS);
|
|
44647
|
+
}
|
|
44642
44648
|
function natsSeedFromEnvironment(env) {
|
|
44643
44649
|
const { NATS_NKEY_SEED_FILE: file, NATS_NKEY_SEED: plain } = env;
|
|
44644
44650
|
if (file !== undefined) {
|
|
@@ -44729,8 +44735,8 @@ function envoySettings(env, config, options = {}) {
|
|
|
44729
44735
|
inbound = { off: messageFor(error) };
|
|
44730
44736
|
}
|
|
44731
44737
|
}
|
|
44732
|
-
const
|
|
44733
|
-
const
|
|
44738
|
+
const heartbeatMs = resolvedInterval(env.ENVOY_HEARTBEAT_MS, DEFAULT_HEARTBEAT_MS);
|
|
44739
|
+
const tmuxPlaceCacheMs = resolvedInterval(env.ENVOY_TMUX_PLACE_CACHE_MS, DEFAULT_TMUX_PLACE_CACHE_MS);
|
|
44734
44740
|
const namedPassDeadlineMs = namedInterval(env.ENVOY_PASS_DEADLINE_MS);
|
|
44735
44741
|
return {
|
|
44736
44742
|
enabled: true,
|
|
@@ -44738,6 +44744,7 @@ function envoySettings(env, config, options = {}) {
|
|
|
44738
44744
|
credential,
|
|
44739
44745
|
inbound,
|
|
44740
44746
|
heartbeatMs,
|
|
44747
|
+
tmuxPlaceCacheMs,
|
|
44741
44748
|
passDeadlineMs: Math.min(Math.max(namedPassDeadlineMs ?? MIN_PASS_DEADLINE_MS, heartbeatMs), MAX_INTERVAL_MS),
|
|
44742
44749
|
passDeadlineRaisedFrom: namedPassDeadlineMs !== undefined && namedPassDeadlineMs < heartbeatMs ? namedPassDeadlineMs : undefined
|
|
44743
44750
|
};
|
|
@@ -45064,6 +45071,7 @@ function createEnvoyClient(config) {
|
|
|
45064
45071
|
...input.delivery === undefined ? {} : { delivery: input.delivery },
|
|
45065
45072
|
...input.clientInstanceID === undefined ? {} : { client_instance_id: input.clientInstanceID },
|
|
45066
45073
|
host: machineID(),
|
|
45074
|
+
...input.place === undefined ? {} : { tmux_place: input.place },
|
|
45067
45075
|
plugin: input.plugin.name,
|
|
45068
45076
|
plugin_version: input.plugin.version
|
|
45069
45077
|
}, { actingSession: input.sessionID }))),
|
|
@@ -46869,9 +46877,12 @@ async function tryExec(exec, file, args, cwd, limitMs) {
|
|
|
46869
46877
|
exec(file, args, { cwd, signal: controller.signal }),
|
|
46870
46878
|
expired.promise
|
|
46871
46879
|
]);
|
|
46872
|
-
|
|
46880
|
+
if (answer === null) {
|
|
46881
|
+
return { failure: "timedOut", ok: false };
|
|
46882
|
+
}
|
|
46883
|
+
return { ok: true, stdout: answer.stdout };
|
|
46873
46884
|
} catch {
|
|
46874
|
-
return
|
|
46885
|
+
return { failure: "refused", ok: false };
|
|
46875
46886
|
} finally {
|
|
46876
46887
|
clearTimeout(timer);
|
|
46877
46888
|
}
|
|
@@ -46912,13 +46923,13 @@ function parseGitHubRemoteUrl(url) {
|
|
|
46912
46923
|
return null;
|
|
46913
46924
|
}
|
|
46914
46925
|
async function resolveCwdRepo(cwd, exec) {
|
|
46915
|
-
const
|
|
46916
|
-
if (
|
|
46917
|
-
const url = selectRemoteUrl(parseRemoteList(
|
|
46926
|
+
const remoteList = await tryExec(exec, "jj", ["git", "remote", "list"], cwd, REMOTE_LOOKUP_MS);
|
|
46927
|
+
if (remoteList.ok) {
|
|
46928
|
+
const url = selectRemoteUrl(parseRemoteList(remoteList.stdout));
|
|
46918
46929
|
return url ? parseGitHubRemoteUrl(url) : null;
|
|
46919
46930
|
}
|
|
46920
|
-
const
|
|
46921
|
-
return
|
|
46931
|
+
const originRemote = await tryExec(exec, "git", ["remote", "get-url", "origin"], cwd, REMOTE_LOOKUP_MS);
|
|
46932
|
+
return originRemote.ok ? parseGitHubRemoteUrl(originRemote.stdout) : null;
|
|
46922
46933
|
}
|
|
46923
46934
|
async function resolveOrigin(env, exec, cwd) {
|
|
46924
46935
|
const origin = {
|
|
@@ -46927,8 +46938,8 @@ async function resolveOrigin(env, exec, cwd) {
|
|
|
46927
46938
|
};
|
|
46928
46939
|
const pane = env.TMUX_PANE;
|
|
46929
46940
|
if (pane) {
|
|
46930
|
-
const
|
|
46931
|
-
const [target, paneId] =
|
|
46941
|
+
const answer = await tryExec(exec, "tmux", ["display-message", "-p", "-t", pane, "#S:#I.#P #{pane_id}"], cwd, TMUX_PANE_LOOKUP_MS);
|
|
46942
|
+
const [target, paneId] = answer.ok ? answer.stdout.trim().split(" ") : [];
|
|
46932
46943
|
if (target)
|
|
46933
46944
|
origin.tmux = target;
|
|
46934
46945
|
if (paneId)
|
|
@@ -47315,11 +47326,19 @@ function relatedCheckLines(suggestions, context) {
|
|
|
47315
47326
|
lines.push(`A past decision may already answer this: ${suggestionLabel(decision)}, answered by ${decision.answered_by}${when} \u2192 ${absoluteHref(decision.href, context.configUrl)}`);
|
|
47316
47327
|
lines.push(...askLines(decision.ask));
|
|
47317
47328
|
}
|
|
47329
|
+
const likely = suggestions.likely_duplicate;
|
|
47330
|
+
if (likely !== undefined && likely !== null) {
|
|
47331
|
+
lines.push(`Likely duplicate: ${likely.key} [${likely.status}] ${likely.title} \u2192 ${absoluteHref(likely.href, context.configUrl)}`);
|
|
47332
|
+
lines.push("Read it. If it is the same work, fold this issue into it by the duplicate rule of skill://dispatch-first.");
|
|
47333
|
+
}
|
|
47334
|
+
if (suggestions.near_copy_check !== undefined && suggestions.near_copy_check !== "") {
|
|
47335
|
+
lines.push(`No near-copy check was made: ${suggestions.near_copy_check}.`);
|
|
47336
|
+
}
|
|
47318
47337
|
for (const match of suggestions.title_matches ?? []) {
|
|
47319
|
-
lines.push(`
|
|
47338
|
+
lines.push(`Near copy you forced past: ${match.key} [${match.status}] ${match.title} \u2192 ${absoluteHref(match.href, context.configUrl)}`);
|
|
47320
47339
|
}
|
|
47321
47340
|
} catch (error) {
|
|
47322
|
-
lines.push(`The check's past decision or
|
|
47341
|
+
lines.push(`The check's past decision or near copies could not be shown: ${messageFor(error)}`);
|
|
47323
47342
|
}
|
|
47324
47343
|
return lines;
|
|
47325
47344
|
}
|
|
@@ -49170,7 +49189,13 @@ function eventHead(event) {
|
|
|
49170
49189
|
return textHead(`${event.payload.name} v${event.payload.version.number}${event.payload.version.summary ? `: ${event.payload.version.summary}` : ""}`);
|
|
49171
49190
|
case "artifact.human_edited":
|
|
49172
49191
|
return textHead(humanEditHead(event.payload));
|
|
49173
|
-
case "issue.created":
|
|
49192
|
+
case "issue.created": {
|
|
49193
|
+
const forced = event.payload.forced;
|
|
49194
|
+
const head = `status ${event.payload.status}`;
|
|
49195
|
+
if (forced === undefined)
|
|
49196
|
+
return head;
|
|
49197
|
+
return forced.past.length === 0 ? `${head} \xB7 forced, the near-copy check did not answer` : `${head} \xB7 forced past ${forced.past.join(", ")}`;
|
|
49198
|
+
}
|
|
49174
49199
|
case "issue.updated":
|
|
49175
49200
|
case "issue.closed":
|
|
49176
49201
|
return `status ${event.payload.status}`;
|
|
@@ -49186,10 +49211,14 @@ function eventRef(event) {
|
|
|
49186
49211
|
const kind = event.type.startsWith("message.") ? "message" : event.type.startsWith("comment.") || event.type.startsWith("suggestion.") ? "comment" : event.type.startsWith("ask.") ? "ask" : undefined;
|
|
49187
49212
|
return kind === undefined ? undefined : `dispatch://${event.issue_key}/${kind}/${id}`;
|
|
49188
49213
|
}
|
|
49214
|
+
function eventActorText(actor) {
|
|
49215
|
+
const title = actor.kind === "session" ? actor.origin?.session_title : undefined;
|
|
49216
|
+
return title === undefined || title === "" ? actorText(actor) : `${actorText(actor)} (${title})`;
|
|
49217
|
+
}
|
|
49189
49218
|
function eventLine(event) {
|
|
49190
49219
|
const head = eventHead(event);
|
|
49191
49220
|
const ref = eventRef(event);
|
|
49192
|
-
return `- #${event.seq} ${event.type}${ref === undefined ? "" : ` ${ref}`} \xB7 ${
|
|
49221
|
+
return `- #${event.seq} ${event.type}${ref === undefined ? "" : ` ${ref}`} \xB7 ${eventActorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
49193
49222
|
}
|
|
49194
49223
|
function childrenSummary(issue) {
|
|
49195
49224
|
return [
|
|
@@ -50006,15 +50035,17 @@ async function executeDispatchTool(input) {
|
|
|
50006
50035
|
const candidates = duplicateCandidates(error);
|
|
50007
50036
|
return {
|
|
50008
50037
|
text: [
|
|
50009
|
-
`Not created: "${title}"
|
|
50038
|
+
`Not created: "${title}" is a near copy of an open issue.`,
|
|
50010
50039
|
...candidates.map((candidate) => {
|
|
50011
50040
|
const href = new URL(candidate.href, linkBase).toString();
|
|
50012
50041
|
return `${candidate.key} [${candidate.status}] ${candidate.title} \u2192 ${href}`;
|
|
50013
50042
|
}),
|
|
50014
|
-
"
|
|
50043
|
+
"Read each issue above. If one is the same work, fold this into it by the duplicate rule of skill://dispatch-first.",
|
|
50044
|
+
"If none is, run dispatch issue again with --force, and write in the new issue's spec why it is not each issue named here."
|
|
50015
50045
|
].join(`
|
|
50016
50046
|
`),
|
|
50017
|
-
details: { duplicates: candidates }
|
|
50047
|
+
details: { duplicates: candidates },
|
|
50048
|
+
refused: true
|
|
50018
50049
|
};
|
|
50019
50050
|
}
|
|
50020
50051
|
}
|
|
@@ -50182,12 +50213,12 @@ async function executeDispatchTool(input) {
|
|
|
50182
50213
|
const project = stringArg(args, "project");
|
|
50183
50214
|
const title = optionalString(args, "title");
|
|
50184
50215
|
const text = optionalString(args, "text");
|
|
50185
|
-
const
|
|
50216
|
+
const excludeIssues = optionalStringArray(args, "exclude_issues");
|
|
50186
50217
|
const keywordsOnly = optionalBoolean(args, "keywords_only");
|
|
50187
50218
|
const suggestions = await client.related(project, {
|
|
50188
50219
|
...title === undefined ? {} : { title },
|
|
50189
50220
|
...text === undefined ? {} : { text },
|
|
50190
|
-
...
|
|
50221
|
+
...excludeIssues === undefined ? {} : { exclude_issues: excludeIssues },
|
|
50191
50222
|
...keywordsOnly === undefined ? {} : { keywords_only: keywordsOnly }
|
|
50192
50223
|
});
|
|
50193
50224
|
return {
|
|
@@ -55145,7 +55176,7 @@ var import_nats2 = __toESM(require_mod4(), 1);
|
|
|
55145
55176
|
|
|
55146
55177
|
// package.json
|
|
55147
55178
|
var name = "@sjawhar/claude-envoy";
|
|
55148
|
-
var version2 = "1.
|
|
55179
|
+
var version2 = "1.14.0";
|
|
55149
55180
|
var legion = {
|
|
55150
55181
|
daemonApiVersion: 55
|
|
55151
55182
|
};
|
|
@@ -57218,9 +57249,63 @@ function replayStateWriter(file, restored) {
|
|
|
57218
57249
|
}
|
|
57219
57250
|
|
|
57220
57251
|
// src/session-title.ts
|
|
57252
|
+
import { basename as basename4 } from "path";
|
|
57253
|
+
|
|
57254
|
+
// ../envoy-client/src/process-ancestry.ts
|
|
57221
57255
|
import { execFileSync } from "child_process";
|
|
57222
57256
|
import { readFileSync as readFileSync9 } from "fs";
|
|
57223
|
-
|
|
57257
|
+
var MAX_ANCESTORS = 64;
|
|
57258
|
+
var PARENT_LOOKUP_MS = 1000;
|
|
57259
|
+
function procParentPid(pid) {
|
|
57260
|
+
try {
|
|
57261
|
+
const stat = readFileSync9(`/proc/${pid}/stat`, "utf8");
|
|
57262
|
+
const parent = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
57263
|
+
return Number.isInteger(parent) ? parent : undefined;
|
|
57264
|
+
} catch {
|
|
57265
|
+
return;
|
|
57266
|
+
}
|
|
57267
|
+
}
|
|
57268
|
+
function processTable() {
|
|
57269
|
+
const table = new Map;
|
|
57270
|
+
try {
|
|
57271
|
+
const listing = execFileSync("ps", ["-Ao", "pid=,ppid="], {
|
|
57272
|
+
encoding: "utf8",
|
|
57273
|
+
killSignal: "SIGKILL",
|
|
57274
|
+
timeout: PARENT_LOOKUP_MS
|
|
57275
|
+
});
|
|
57276
|
+
for (const line of listing.split(`
|
|
57277
|
+
`)) {
|
|
57278
|
+
const [, pid = "", parent = ""] = line.match(/^\s*(\d+)\s+(\d+)\s*$/) ?? [];
|
|
57279
|
+
if (pid !== "")
|
|
57280
|
+
table.set(Number(pid), Number(parent));
|
|
57281
|
+
}
|
|
57282
|
+
} catch {}
|
|
57283
|
+
return table;
|
|
57284
|
+
}
|
|
57285
|
+
function isAncestor(candidate, pid = process.pid) {
|
|
57286
|
+
let table;
|
|
57287
|
+
const parentPid = (child) => {
|
|
57288
|
+
if (process.platform === "linux")
|
|
57289
|
+
return procParentPid(child);
|
|
57290
|
+
table ??= processTable();
|
|
57291
|
+
return table.get(child);
|
|
57292
|
+
};
|
|
57293
|
+
let current = pid;
|
|
57294
|
+
for (let step = 0;current !== undefined && current > 1 && step < MAX_ANCESTORS; step++) {
|
|
57295
|
+
if (String(current) === candidate)
|
|
57296
|
+
return true;
|
|
57297
|
+
current = parentPid(current);
|
|
57298
|
+
}
|
|
57299
|
+
return false;
|
|
57300
|
+
}
|
|
57301
|
+
|
|
57302
|
+
// ../envoy-client/src/tmux-server.ts
|
|
57303
|
+
function tmuxServer(value) {
|
|
57304
|
+
const parts = (value ?? "").split(",");
|
|
57305
|
+
return { serverPid: parts.at(-2) ?? "", socket: parts.slice(0, -2).join(",") };
|
|
57306
|
+
}
|
|
57307
|
+
|
|
57308
|
+
// src/session-title.ts
|
|
57224
57309
|
var TMUX_PANE_FORMAT = [
|
|
57225
57310
|
"#{session_name}",
|
|
57226
57311
|
"#{window_name}",
|
|
@@ -57231,7 +57316,6 @@ var TMUX_PANE_FORMAT = [
|
|
|
57231
57316
|
].join("\t");
|
|
57232
57317
|
var TMUX_TIMEOUT_MS = 1000;
|
|
57233
57318
|
var SHORT_ID_LENGTH = 8;
|
|
57234
|
-
var MAX_ANCESTORS = 64;
|
|
57235
57319
|
var CSI_SEQUENCE = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "gu");
|
|
57236
57320
|
function oneLine(value) {
|
|
57237
57321
|
return value.replace(CSI_SEQUENCE, "").replace(/[\u202a-\u202e\u2066-\u2069]/gu, "").replace(/[\s\p{Cc}]+/gu, " ").trim();
|
|
@@ -57239,32 +57323,6 @@ function oneLine(value) {
|
|
|
57239
57323
|
function visible(value) {
|
|
57240
57324
|
return value.replace(/[\s\p{Cc}\p{Cf}\p{Cs}\p{Cn}\p{M}\p{Default_Ignorable_Code_Point}\u2800]/gu, "") !== "";
|
|
57241
57325
|
}
|
|
57242
|
-
function parentPid(pid) {
|
|
57243
|
-
try {
|
|
57244
|
-
if (process.platform === "linux") {
|
|
57245
|
-
const stat = readFileSync9(`/proc/${pid}/stat`, "utf8");
|
|
57246
|
-
const parent = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
57247
|
-
return Number.isInteger(parent) ? parent : undefined;
|
|
57248
|
-
}
|
|
57249
|
-
const parent = Number(execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], {
|
|
57250
|
-
encoding: "utf8",
|
|
57251
|
-
timeout: TMUX_TIMEOUT_MS,
|
|
57252
|
-
killSignal: "SIGKILL"
|
|
57253
|
-
}).trim());
|
|
57254
|
-
return Number.isInteger(parent) ? parent : undefined;
|
|
57255
|
-
} catch {
|
|
57256
|
-
return;
|
|
57257
|
-
}
|
|
57258
|
-
}
|
|
57259
|
-
function isAncestor(candidate, pid = process.pid) {
|
|
57260
|
-
let current = pid;
|
|
57261
|
-
for (let step = 0;current !== undefined && current > 1 && step < MAX_ANCESTORS; step++) {
|
|
57262
|
-
if (String(current) === candidate)
|
|
57263
|
-
return true;
|
|
57264
|
-
current = parentPid(current);
|
|
57265
|
-
}
|
|
57266
|
-
return false;
|
|
57267
|
-
}
|
|
57268
57326
|
async function queryTmux(socket, pane, format) {
|
|
57269
57327
|
let child;
|
|
57270
57328
|
try {
|
|
@@ -57328,9 +57386,7 @@ async function sessionTitle(environment, directory, sources = {}) {
|
|
|
57328
57386
|
const explicit = oneLine(configuredValue(environment.ENVOY_SESSION_TITLE) ?? "");
|
|
57329
57387
|
if (visible(explicit))
|
|
57330
57388
|
return () => explicit;
|
|
57331
|
-
const
|
|
57332
|
-
const socket = parts.slice(0, -2).join(",");
|
|
57333
|
-
const serverPid = parts.at(-2) ?? "";
|
|
57389
|
+
const { serverPid, socket } = tmuxServer(configuredValue(environment.TMUX));
|
|
57334
57390
|
const pane = configuredValue(environment.TMUX_PANE)?.trim();
|
|
57335
57391
|
const directoryName = oneLine(basename4(directory) || directory);
|
|
57336
57392
|
const name = (socket === "" || serverPid === "" || pane === undefined ? undefined : await tmuxPaneTitle(socket, serverPid, pane, ownPane, query)) ?? (visible(directoryName) ? directoryName : "session");
|