@groundnuty/macf-channel-server 0.2.0-rc.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/dist/collision.d.ts +25 -0
- package/dist/collision.d.ts.map +1 -0
- package/dist/collision.js +94 -0
- package/dist/collision.js.map +1 -0
- package/dist/health.d.ts +3 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +33 -0
- package/dist/health.js.map +1 -0
- package/dist/https.d.ts +25 -0
- package/dist/https.d.ts.map +1 -0
- package/dist/https.js +361 -0
- package/dist/https.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +43 -0
- package/dist/mcp.js.map +1 -0
- package/dist/notify-formatter.d.ts +19 -0
- package/dist/notify-formatter.d.ts.map +1 -0
- package/dist/notify-formatter.js +43 -0
- package/dist/notify-formatter.js.map +1 -0
- package/dist/otel.d.ts +59 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +122 -0
- package/dist/otel.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +256 -0
- package/dist/server.js.map +1 -0
- package/dist/shutdown.d.ts +15 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +53 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/startup-issues.d.ts +24 -0
- package/dist/startup-issues.d.ts.map +1 -0
- package/dist/startup-issues.js +75 -0
- package/dist/startup-issues.js.map +1 -0
- package/dist/tmux-wake.d.ts +106 -0
- package/dist/tmux-wake.d.ts.map +1 -0
- package/dist/tmux-wake.js +196 -0
- package/dist/tmux-wake.js.map +1 -0
- package/dist/tracing.d.ts +38 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +68 -0
- package/dist/tracing.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Registry } from '@groundnuty/macf-core';
|
|
2
|
+
import type { HttpsServer, Logger } from '@groundnuty/macf-core';
|
|
3
|
+
/**
|
|
4
|
+
* Registers SIGTERM and SIGINT handlers that clean up the agent's
|
|
5
|
+
* registry variable and stop the HTTPS server.
|
|
6
|
+
*
|
|
7
|
+
* Returns a cleanup function that can also be called directly.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerShutdownHandler(config: {
|
|
10
|
+
readonly agentName: string;
|
|
11
|
+
readonly registry: Registry;
|
|
12
|
+
readonly httpsServer: HttpsServer;
|
|
13
|
+
readonly logger: Logger;
|
|
14
|
+
}): () => Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAqDzB"}
|
package/dist/shutdown.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers SIGTERM and SIGINT handlers that clean up the agent's
|
|
3
|
+
* registry variable and stop the HTTPS server.
|
|
4
|
+
*
|
|
5
|
+
* Returns a cleanup function that can also be called directly.
|
|
6
|
+
*/
|
|
7
|
+
export function registerShutdownHandler(config) {
|
|
8
|
+
const { agentName, registry, httpsServer, logger } = config;
|
|
9
|
+
let shuttingDown = false;
|
|
10
|
+
let lastResult = true;
|
|
11
|
+
async function cleanup() {
|
|
12
|
+
if (shuttingDown)
|
|
13
|
+
return lastResult;
|
|
14
|
+
shuttingDown = true;
|
|
15
|
+
logger.info('shutdown_start', { agent: agentName });
|
|
16
|
+
let ok = true;
|
|
17
|
+
try {
|
|
18
|
+
await registry.remove(agentName);
|
|
19
|
+
logger.info('shutdown_deregistered', { agent: agentName });
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
logger.error('shutdown_deregister_failed', {
|
|
23
|
+
agent: agentName,
|
|
24
|
+
error: err instanceof Error ? err.message : String(err),
|
|
25
|
+
});
|
|
26
|
+
ok = false;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await httpsServer.stop();
|
|
30
|
+
logger.info('shutdown_server_stopped', { agent: agentName });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
logger.error('shutdown_server_stop_failed', {
|
|
34
|
+
agent: agentName,
|
|
35
|
+
error: err instanceof Error ? err.message : String(err),
|
|
36
|
+
});
|
|
37
|
+
ok = false;
|
|
38
|
+
}
|
|
39
|
+
logger.info('shutdown_complete', { agent: agentName, ok });
|
|
40
|
+
lastResult = ok;
|
|
41
|
+
return ok;
|
|
42
|
+
}
|
|
43
|
+
// Exit 1 when any cleanup step failed so external monitors (systemd,
|
|
44
|
+
// macf-actions heartbeat) surface the degraded state instead of
|
|
45
|
+
// silently absorbing it into a clean exit (#103 R2).
|
|
46
|
+
const handler = () => {
|
|
47
|
+
cleanup().then(ok => process.exit(ok ? 0 : 1), () => process.exit(1));
|
|
48
|
+
};
|
|
49
|
+
process.on('SIGTERM', handler);
|
|
50
|
+
process.on('SIGINT', handler);
|
|
51
|
+
return cleanup;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=shutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAKvC;IACC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC5D,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,UAAU,GAAG,IAAI,CAAC;IAEtB,KAAK,UAAU,OAAO;QACpB,IAAI,YAAY;YAAE,OAAO,UAAU,CAAC;QACpC,YAAY,GAAG,IAAI,CAAC;QAEpB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,IAAI,EAAE,GAAG,IAAI,CAAC;QAEd,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE;gBACzC,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;QAED,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE;gBAC1C,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3D,UAAU,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,qEAAqE;IACrE,gEAAgE;IAChE,qDAAqD;IACrD,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,OAAO,EAAE,CAAC,IAAI,CACZ,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAC9B,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CACtB,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE9B,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Logger } from '@groundnuty/macf-core';
|
|
2
|
+
/**
|
|
3
|
+
* Query GitHub for open issues assigned to this agent and push
|
|
4
|
+
* a startup_check notification for each one.
|
|
5
|
+
*
|
|
6
|
+
* @deprecated Not invoked by the server since macf#192. Use the
|
|
7
|
+
* marketplace plugin's `session-start-pickup.sh` SessionStart hook
|
|
8
|
+
* instead (handles per-agent-label + multi-repo installation
|
|
9
|
+
* enumeration correctly).
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkPendingIssues(config: {
|
|
12
|
+
readonly repo: string;
|
|
13
|
+
readonly agentLabel: string;
|
|
14
|
+
readonly token: string;
|
|
15
|
+
readonly onNotify: (payload: {
|
|
16
|
+
readonly type: 'startup_check';
|
|
17
|
+
readonly message: string;
|
|
18
|
+
readonly issue_number: number;
|
|
19
|
+
readonly title: string;
|
|
20
|
+
readonly source: string;
|
|
21
|
+
}) => Promise<void>;
|
|
22
|
+
readonly logger: Logger;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=startup-issues.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup-issues.d.ts","sourceRoot":"","sources":["../src/startup-issues.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AASpD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;QAC3B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;QAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEPRECATED (macf#192, 2026-04-22): the server no longer invokes
|
|
3
|
+
* `checkPendingIssues` at boot. The prior call path hardcoded
|
|
4
|
+
* `repo: 'groundnuty/macf', agentLabel: 'code-agent'`, which caused
|
|
5
|
+
* cross-agent noise (every agent queried macf's code-agent issues at
|
|
6
|
+
* startup regardless of identity).
|
|
7
|
+
*
|
|
8
|
+
* Replaced by the marketplace v0.1.7 `session-start-pickup.sh`
|
|
9
|
+
* SessionStart hook, which queries per-agent-label across every
|
|
10
|
+
* repo the agent's App installation covers (via
|
|
11
|
+
* `gh api /installation/repositories`). Plugin-side is the right
|
|
12
|
+
* layer for this — the agent's installation set is the single
|
|
13
|
+
* source of truth for which repos are in scope; server-side code
|
|
14
|
+
* doesn't need to know.
|
|
15
|
+
*
|
|
16
|
+
* This module remains exported for API back-compat — any external
|
|
17
|
+
* consumer that imported `checkPendingIssues` via the public
|
|
18
|
+
* `@macf/cli` index keeps working. Function may be removed in a
|
|
19
|
+
* future major bump if no consumer surfaces.
|
|
20
|
+
*/
|
|
21
|
+
import { execFile } from 'node:child_process';
|
|
22
|
+
import { promisify } from 'node:util';
|
|
23
|
+
const execFileAsync = promisify(execFile);
|
|
24
|
+
/**
|
|
25
|
+
* Query GitHub for open issues assigned to this agent and push
|
|
26
|
+
* a startup_check notification for each one.
|
|
27
|
+
*
|
|
28
|
+
* @deprecated Not invoked by the server since macf#192. Use the
|
|
29
|
+
* marketplace plugin's `session-start-pickup.sh` SessionStart hook
|
|
30
|
+
* instead (handles per-agent-label + multi-repo installation
|
|
31
|
+
* enumeration correctly).
|
|
32
|
+
*/
|
|
33
|
+
export async function checkPendingIssues(config) {
|
|
34
|
+
const { repo, agentLabel, token, onNotify, logger } = config;
|
|
35
|
+
let issues;
|
|
36
|
+
try {
|
|
37
|
+
const { stdout } = await execFileAsync('gh', [
|
|
38
|
+
'issue', 'list',
|
|
39
|
+
'--repo', repo,
|
|
40
|
+
'--label', agentLabel,
|
|
41
|
+
'--state', 'open',
|
|
42
|
+
'--json', 'number,title',
|
|
43
|
+
], {
|
|
44
|
+
encoding: 'utf-8',
|
|
45
|
+
env: { ...process.env, GH_TOKEN: token },
|
|
46
|
+
});
|
|
47
|
+
issues = JSON.parse(stdout);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
logger.warn('startup_issues_check_failed', {
|
|
51
|
+
error: err instanceof Error ? err.message : String(err),
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (issues.length === 0) {
|
|
56
|
+
logger.info('startup_issues_none', { repo, label: agentLabel });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
logger.info('startup_issues_found', { count: issues.length });
|
|
60
|
+
// One routing event per queued issue (#103 R1). Pre-fix, only the
|
|
61
|
+
// first issue's metadata was delivered to the router — extras were
|
|
62
|
+
// silently dropped. Sequential delivery matches the natural 1-issue-
|
|
63
|
+
// 1-routing-event model and keeps the router's per-issue context
|
|
64
|
+
// separate.
|
|
65
|
+
for (const issue of issues) {
|
|
66
|
+
await onNotify({
|
|
67
|
+
type: 'startup_check',
|
|
68
|
+
message: `Pending issue #${issue.number}: ${issue.title}`,
|
|
69
|
+
issue_number: issue.number,
|
|
70
|
+
title: issue.title,
|
|
71
|
+
source: 'startup',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=startup-issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup-issues.js","sourceRoot":"","sources":["../src/startup-issues.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAO1C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAYxC;IACC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAE7D,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE;YAC3C,OAAO,EAAE,MAAM;YACf,QAAQ,EAAE,IAAI;YACd,SAAS,EAAE,UAAU;YACrB,SAAS,EAAE,MAAM;YACjB,QAAQ,EAAE,cAAc;SACzB,EAAE;YACD,QAAQ,EAAE,OAAO;YACjB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE;SACzC,CAAC,CAAC;QAEH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA4B,CAAC;IACzD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE;YACzC,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9D,kEAAkE;IAClE,mEAAmE;IACnE,qEAAqE;IACrE,iEAAiE;IACjE,YAAY;IACZ,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,CAAC;YACb,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,kBAAkB,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,KAAK,EAAE;YACzD,YAAY,EAAE,KAAK,CAAC,MAAM;YAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic prompt injection into a running Claude Code TUI via the
|
|
3
|
+
* canonical `tmux-send-to-claude.sh` helper — "sidecar wake" sibling to
|
|
4
|
+
* the MCP notification push.
|
|
5
|
+
*
|
|
6
|
+
* Background (macf#185):
|
|
7
|
+
*
|
|
8
|
+
* Phase 7's mTLS /notify delivery works end-to-end: routing Action
|
|
9
|
+
* mTLS-POSTs to a peer agent's channel server, server receives the
|
|
10
|
+
* payload, pushes it through the MCP channel. But Claude Code's
|
|
11
|
+
* running TUI never sees the notification as a new prompt — the MCP
|
|
12
|
+
* push deposits data into the channel-server-observable state,
|
|
13
|
+
* doesn't interrupt the running session with a fresh prompt.
|
|
14
|
+
*
|
|
15
|
+
* Users expected /notify to "wake the agent to work on the new
|
|
16
|
+
* thing." What actually happened: the /notify was queue-deposit, not
|
|
17
|
+
* wake. Agents only processed new work when the human operator
|
|
18
|
+
* manually ran `tmux-send-to-claude.sh` (or killed + relaunched the
|
|
19
|
+
* TUI so the SessionStart auto-pickup hook fired).
|
|
20
|
+
*
|
|
21
|
+
* Fix shape (this module):
|
|
22
|
+
*
|
|
23
|
+
* Extend the `onNotify` callback chain with a second step — after
|
|
24
|
+
* the MCP push completes, shell out to `tmux-send-to-claude.sh` with
|
|
25
|
+
* a human-readable prompt synthesized from the NotifyPayload. The
|
|
26
|
+
* running Claude TUI receives the prompt text in its input buffer
|
|
27
|
+
* just as if the operator had typed it, and processes it as the
|
|
28
|
+
* next turn.
|
|
29
|
+
*
|
|
30
|
+
* Target discovery:
|
|
31
|
+
*
|
|
32
|
+
* 1. Explicit via `MACF_TMUX_SESSION` / `MACF_TMUX_WINDOW` env
|
|
33
|
+
* (sourced from `macf-agent.json`'s `tmux_session` /
|
|
34
|
+
* `tmux_window` fields, emitted by `claude.sh`). Operator-
|
|
35
|
+
* declared target.
|
|
36
|
+
* 2. Auto-detect from `TMUX` env + `tmux display-message -p`. Works
|
|
37
|
+
* when the server was launched from within a tmux pane (the
|
|
38
|
+
* canonical MACF launch pattern: `tmux new -d -s cv-architect
|
|
39
|
+
* './claude.sh'`). Zero-config for this case.
|
|
40
|
+
* 3. Neither available → no-op, return `false`. /notify's 200
|
|
41
|
+
* response reflects "accepted into MCP", not "delivered to
|
|
42
|
+
* human TUI" — operators without tmux get the MCP notification
|
|
43
|
+
* in the channel-server state + the old manual-check UX.
|
|
44
|
+
*
|
|
45
|
+
* Fail-silent policy:
|
|
46
|
+
*
|
|
47
|
+
* Every error path (missing helper script, tmux not installed, pane
|
|
48
|
+
* gone, exit-code-nonzero) logs at debug level + returns `false`.
|
|
49
|
+
* Never throws. /notify's contract with the caller is unaffected —
|
|
50
|
+
* we return 200 regardless of wake-path outcome.
|
|
51
|
+
*/
|
|
52
|
+
import type { Logger } from '@groundnuty/macf-core';
|
|
53
|
+
export interface WakeOptions {
|
|
54
|
+
/** Workspace root for locating `.claude/scripts/tmux-send-to-claude.sh`. */
|
|
55
|
+
readonly workspaceDir: string;
|
|
56
|
+
/** Explicit tmux session name (e.g. "cv-project"). Optional. */
|
|
57
|
+
readonly session?: string;
|
|
58
|
+
/** Explicit tmux window index/name (e.g. "0", "cv-architect"). Optional. */
|
|
59
|
+
readonly window?: string;
|
|
60
|
+
/** Debug logger. Info/warn events on success paths; debug on skips. */
|
|
61
|
+
readonly logger: Logger;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the `tmux-send-to-claude.sh` target argument from explicit env
|
|
65
|
+
* + auto-detection fallback. Returns the target string to pass as the
|
|
66
|
+
* script's first arg, or `null` if no target resolvable.
|
|
67
|
+
*
|
|
68
|
+
* Priority order:
|
|
69
|
+
*
|
|
70
|
+
* 1. Explicit `session` + optional `window`
|
|
71
|
+
* → `"session:window"` / `"session"`
|
|
72
|
+
* 2. `$TMUX_PANE` set (e.g. `%87`)
|
|
73
|
+
* → that pane ID — deterministic per-pane identity, inherited
|
|
74
|
+
* by every child of the pane. This is the ground truth when
|
|
75
|
+
* the server was launched inside a tmux pane: no matter how
|
|
76
|
+
* many sessions/windows exist or whether `display-message`
|
|
77
|
+
* would resolve ambiguously, `$TMUX_PANE` points at exactly
|
|
78
|
+
* the pane our process belongs to. See macf#189 sub-item 3 —
|
|
79
|
+
* the bilateral e2e demo exposed the ambiguity of
|
|
80
|
+
* `display-message` on a shared tmux socket with multiple
|
|
81
|
+
* windows; wake landed on the wrong pane.
|
|
82
|
+
* 3. `$TMUX` set (generic tmux presence)
|
|
83
|
+
* → fall back to `tmux display-message -p '...'` for the
|
|
84
|
+
* common case where `$TMUX_PANE` isn't exported (older tmux
|
|
85
|
+
* or non-interactive invocations).
|
|
86
|
+
* 4. None of the above
|
|
87
|
+
* → null (wake path no-ops; log "no_target" skip).
|
|
88
|
+
*
|
|
89
|
+
* Exported for unit tests.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveTmuxTarget(opts: {
|
|
92
|
+
readonly session?: string;
|
|
93
|
+
readonly window?: string;
|
|
94
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
95
|
+
}): string | null;
|
|
96
|
+
/**
|
|
97
|
+
* Wake a running Claude Code TUI by injecting a prompt via the
|
|
98
|
+
* canonical tmux helper. Returns `true` when the helper ran
|
|
99
|
+
* successfully, `false` on any no-op or error path.
|
|
100
|
+
*
|
|
101
|
+
* `prompt` is text-only — becomes the TUI's next input. Newlines,
|
|
102
|
+
* quotes, shell-metacharacters are all safe because we pass the
|
|
103
|
+
* prompt as a separate argv to the helper (no shell interpolation).
|
|
104
|
+
*/
|
|
105
|
+
export declare function wakeViaTmux(prompt: string, opts: WakeOptions): boolean;
|
|
106
|
+
//# sourceMappingURL=tmux-wake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux-wake.d.ts","sourceRoot":"","sources":["../src/tmux-wake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAClC,GAAG,MAAM,GAAG,IAAI,CAiChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAwEtE"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic prompt injection into a running Claude Code TUI via the
|
|
3
|
+
* canonical `tmux-send-to-claude.sh` helper — "sidecar wake" sibling to
|
|
4
|
+
* the MCP notification push.
|
|
5
|
+
*
|
|
6
|
+
* Background (macf#185):
|
|
7
|
+
*
|
|
8
|
+
* Phase 7's mTLS /notify delivery works end-to-end: routing Action
|
|
9
|
+
* mTLS-POSTs to a peer agent's channel server, server receives the
|
|
10
|
+
* payload, pushes it through the MCP channel. But Claude Code's
|
|
11
|
+
* running TUI never sees the notification as a new prompt — the MCP
|
|
12
|
+
* push deposits data into the channel-server-observable state,
|
|
13
|
+
* doesn't interrupt the running session with a fresh prompt.
|
|
14
|
+
*
|
|
15
|
+
* Users expected /notify to "wake the agent to work on the new
|
|
16
|
+
* thing." What actually happened: the /notify was queue-deposit, not
|
|
17
|
+
* wake. Agents only processed new work when the human operator
|
|
18
|
+
* manually ran `tmux-send-to-claude.sh` (or killed + relaunched the
|
|
19
|
+
* TUI so the SessionStart auto-pickup hook fired).
|
|
20
|
+
*
|
|
21
|
+
* Fix shape (this module):
|
|
22
|
+
*
|
|
23
|
+
* Extend the `onNotify` callback chain with a second step — after
|
|
24
|
+
* the MCP push completes, shell out to `tmux-send-to-claude.sh` with
|
|
25
|
+
* a human-readable prompt synthesized from the NotifyPayload. The
|
|
26
|
+
* running Claude TUI receives the prompt text in its input buffer
|
|
27
|
+
* just as if the operator had typed it, and processes it as the
|
|
28
|
+
* next turn.
|
|
29
|
+
*
|
|
30
|
+
* Target discovery:
|
|
31
|
+
*
|
|
32
|
+
* 1. Explicit via `MACF_TMUX_SESSION` / `MACF_TMUX_WINDOW` env
|
|
33
|
+
* (sourced from `macf-agent.json`'s `tmux_session` /
|
|
34
|
+
* `tmux_window` fields, emitted by `claude.sh`). Operator-
|
|
35
|
+
* declared target.
|
|
36
|
+
* 2. Auto-detect from `TMUX` env + `tmux display-message -p`. Works
|
|
37
|
+
* when the server was launched from within a tmux pane (the
|
|
38
|
+
* canonical MACF launch pattern: `tmux new -d -s cv-architect
|
|
39
|
+
* './claude.sh'`). Zero-config for this case.
|
|
40
|
+
* 3. Neither available → no-op, return `false`. /notify's 200
|
|
41
|
+
* response reflects "accepted into MCP", not "delivered to
|
|
42
|
+
* human TUI" — operators without tmux get the MCP notification
|
|
43
|
+
* in the channel-server state + the old manual-check UX.
|
|
44
|
+
*
|
|
45
|
+
* Fail-silent policy:
|
|
46
|
+
*
|
|
47
|
+
* Every error path (missing helper script, tmux not installed, pane
|
|
48
|
+
* gone, exit-code-nonzero) logs at debug level + returns `false`.
|
|
49
|
+
* Never throws. /notify's contract with the caller is unaffected —
|
|
50
|
+
* we return 200 regardless of wake-path outcome.
|
|
51
|
+
*/
|
|
52
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
53
|
+
import { existsSync } from 'node:fs';
|
|
54
|
+
import { join } from 'node:path';
|
|
55
|
+
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
56
|
+
import { getTracer, SpanNames, Attr } from './tracing.js';
|
|
57
|
+
/**
|
|
58
|
+
* Build the `tmux-send-to-claude.sh` target argument from explicit env
|
|
59
|
+
* + auto-detection fallback. Returns the target string to pass as the
|
|
60
|
+
* script's first arg, or `null` if no target resolvable.
|
|
61
|
+
*
|
|
62
|
+
* Priority order:
|
|
63
|
+
*
|
|
64
|
+
* 1. Explicit `session` + optional `window`
|
|
65
|
+
* → `"session:window"` / `"session"`
|
|
66
|
+
* 2. `$TMUX_PANE` set (e.g. `%87`)
|
|
67
|
+
* → that pane ID — deterministic per-pane identity, inherited
|
|
68
|
+
* by every child of the pane. This is the ground truth when
|
|
69
|
+
* the server was launched inside a tmux pane: no matter how
|
|
70
|
+
* many sessions/windows exist or whether `display-message`
|
|
71
|
+
* would resolve ambiguously, `$TMUX_PANE` points at exactly
|
|
72
|
+
* the pane our process belongs to. See macf#189 sub-item 3 —
|
|
73
|
+
* the bilateral e2e demo exposed the ambiguity of
|
|
74
|
+
* `display-message` on a shared tmux socket with multiple
|
|
75
|
+
* windows; wake landed on the wrong pane.
|
|
76
|
+
* 3. `$TMUX` set (generic tmux presence)
|
|
77
|
+
* → fall back to `tmux display-message -p '...'` for the
|
|
78
|
+
* common case where `$TMUX_PANE` isn't exported (older tmux
|
|
79
|
+
* or non-interactive invocations).
|
|
80
|
+
* 4. None of the above
|
|
81
|
+
* → null (wake path no-ops; log "no_target" skip).
|
|
82
|
+
*
|
|
83
|
+
* Exported for unit tests.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveTmuxTarget(opts) {
|
|
86
|
+
const env = opts.env ?? process.env;
|
|
87
|
+
if (opts.session !== undefined && opts.session !== '') {
|
|
88
|
+
const target = opts.window !== undefined && opts.window !== ''
|
|
89
|
+
? `${opts.session}:${opts.window}`
|
|
90
|
+
: opts.session;
|
|
91
|
+
return target;
|
|
92
|
+
}
|
|
93
|
+
// TMUX_PANE is the most deterministic auto-detect: tmux sets it
|
|
94
|
+
// per-pane (e.g. `%87`) and every child process of the pane
|
|
95
|
+
// inherits it. A pane ID is a valid `tmux -t` target, so we can
|
|
96
|
+
// pass it straight through to the helper.
|
|
97
|
+
const pane = env['TMUX_PANE'];
|
|
98
|
+
if (pane !== undefined && pane !== '')
|
|
99
|
+
return pane;
|
|
100
|
+
// Fall back to display-message when $TMUX is set but $TMUX_PANE
|
|
101
|
+
// isn't (older tmux versions or unusual launch paths).
|
|
102
|
+
if (!env['TMUX'])
|
|
103
|
+
return null;
|
|
104
|
+
try {
|
|
105
|
+
const out = execFileSync('tmux', ['display-message', '-p', '#{session_name}:#{window_index}'], {
|
|
106
|
+
encoding: 'utf-8',
|
|
107
|
+
// Tight timeout — tmux display-message is instant locally.
|
|
108
|
+
timeout: 2000,
|
|
109
|
+
});
|
|
110
|
+
const target = out.trim();
|
|
111
|
+
// display-message returns ":0" or similar when no session context
|
|
112
|
+
// is available; guard against those degenerate cases.
|
|
113
|
+
if (target === '' || target.startsWith(':'))
|
|
114
|
+
return null;
|
|
115
|
+
return target;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Wake a running Claude Code TUI by injecting a prompt via the
|
|
123
|
+
* canonical tmux helper. Returns `true` when the helper ran
|
|
124
|
+
* successfully, `false` on any no-op or error path.
|
|
125
|
+
*
|
|
126
|
+
* `prompt` is text-only — becomes the TUI's next input. Newlines,
|
|
127
|
+
* quotes, shell-metacharacters are all safe because we pass the
|
|
128
|
+
* prompt as a separate argv to the helper (no shell interpolation).
|
|
129
|
+
*/
|
|
130
|
+
export function wakeViaTmux(prompt, opts) {
|
|
131
|
+
const tracer = getTracer();
|
|
132
|
+
// startActiveSpan with sync callback: the wake-path is sync shell-
|
|
133
|
+
// out via spawnSync, so no async context-propagation complexity.
|
|
134
|
+
// Span attached to whatever parent is active (typically
|
|
135
|
+
// macf.server.notify_received when called from the onNotify chain).
|
|
136
|
+
return tracer.startActiveSpan(SpanNames.TmuxWakeDeliver, { kind: SpanKind.INTERNAL }, (span) => {
|
|
137
|
+
try {
|
|
138
|
+
const scriptPath = join(opts.workspaceDir, '.claude', 'scripts', 'tmux-send-to-claude.sh');
|
|
139
|
+
if (!existsSync(scriptPath)) {
|
|
140
|
+
opts.logger.info('tmux_wake_skipped', {
|
|
141
|
+
reason: 'helper_missing',
|
|
142
|
+
path: scriptPath,
|
|
143
|
+
});
|
|
144
|
+
span.setAttribute(Attr.WakeOutcome, 'helper_missing');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const target = resolveTmuxTarget({ session: opts.session, window: opts.window });
|
|
148
|
+
if (target === null) {
|
|
149
|
+
opts.logger.info('tmux_wake_skipped', {
|
|
150
|
+
reason: 'no_target',
|
|
151
|
+
detail: 'MACF_TMUX_SESSION unset and $TMUX auto-detect unavailable',
|
|
152
|
+
});
|
|
153
|
+
span.setAttribute(Attr.WakeOutcome, 'no_target');
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
span.setAttribute(Attr.TmuxTarget, target);
|
|
157
|
+
const result = spawnSync(scriptPath, [target, prompt], {
|
|
158
|
+
encoding: 'utf-8',
|
|
159
|
+
// Helper sleeps 1s between the two Enters; give headroom for
|
|
160
|
+
// pane startup + tmux IPC.
|
|
161
|
+
timeout: 10_000,
|
|
162
|
+
});
|
|
163
|
+
if (result.error !== undefined) {
|
|
164
|
+
opts.logger.warn('tmux_wake_failed', {
|
|
165
|
+
reason: 'spawn_error',
|
|
166
|
+
error: result.error.message,
|
|
167
|
+
});
|
|
168
|
+
span.setAttribute(Attr.WakeOutcome, 'spawn_error');
|
|
169
|
+
span.recordException(result.error);
|
|
170
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error.message });
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (result.status !== 0) {
|
|
174
|
+
opts.logger.warn('tmux_wake_failed', {
|
|
175
|
+
reason: 'nonzero_exit',
|
|
176
|
+
status: result.status,
|
|
177
|
+
stderr: result.stderr.slice(0, 200),
|
|
178
|
+
});
|
|
179
|
+
span.setAttribute(Attr.WakeOutcome, 'nonzero_exit');
|
|
180
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: `exit ${String(result.status)}` });
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
opts.logger.info('tmux_wake_delivered', {
|
|
184
|
+
target,
|
|
185
|
+
prompt_length: prompt.length,
|
|
186
|
+
});
|
|
187
|
+
span.setAttribute(Attr.WakeOutcome, 'delivered');
|
|
188
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
span.end();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=tmux-wake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux-wake.js","sourceRoot":"","sources":["../src/tmux-wake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAa1D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAIjC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE;YAC5D,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE;YAClC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,gEAAgE;IAChE,4DAA4D;IAC5D,gEAAgE;IAChE,0CAA0C;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAEnD,gEAAgE;IAChE,uDAAuD;IACvD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,iBAAiB,EAAE,IAAI,EAAE,iCAAiC,CAAC,EAAE;YAC7F,QAAQ,EAAE,OAAO;YACjB,2DAA2D;YAC3D,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC1B,kEAAkE;QAClE,sDAAsD;QACtD,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACzD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAiB;IAC3D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,mEAAmE;IACnE,iEAAiE;IACjE,wDAAwD;IACxD,oEAAoE;IACpE,OAAO,MAAM,CAAC,eAAe,CAC3B,SAAS,CAAC,eAAe,EACzB,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAC3B,CAAC,IAAI,EAAW,EAAE;QAChB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,wBAAwB,CAAC,CAAC;YAC3F,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;oBACpC,MAAM,EAAE,gBAAgB;oBACxB,IAAI,EAAE,UAAU;iBACjB,CAAC,CAAC;gBACH,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;gBACtD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACjF,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;oBACpC,MAAM,EAAE,WAAW;oBACnB,MAAM,EAAE,2DAA2D;iBACpE,CAAC,CAAC;gBACH,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;gBACjD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAE3C,MAAM,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;gBACrD,QAAQ,EAAE,OAAO;gBACjB,6DAA6D;gBAC7D,2BAA2B;gBAC3B,OAAO,EAAE,MAAM;aAChB,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE;oBACnC,MAAM,EAAE,aAAa;oBACrB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;iBAC5B,CAAC,CAAC;gBACH,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;gBACnD,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC9E,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE;oBACnC,MAAM,EAAE,cAAc;oBACtB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;iBACpC,CAAC,CAAC;gBACH,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;gBACpD,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzF,OAAO,KAAK,CAAC;YACf,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE;gBACtC,MAAM;gBACN,aAAa,EAAE,MAAM,CAAC,MAAM;aAC7B,CAAC,CAAC;YACH,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;YACjD,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import type { NotifyPayload } from '@groundnuty/macf-core';
|
|
3
|
+
/** The single tracer instance used across the MACF codebase. */
|
|
4
|
+
export declare function getTracer(): Tracer;
|
|
5
|
+
/** Span names — centralized for easy rename + grep. */
|
|
6
|
+
export declare const SpanNames: {
|
|
7
|
+
readonly NotifyReceived: "macf.server.notify_received";
|
|
8
|
+
readonly SignCsr: "macf.server.sign_csr";
|
|
9
|
+
readonly StartupRegister: "macf.server.register";
|
|
10
|
+
readonly StartupCollisionCheck: "macf.server.collision_check";
|
|
11
|
+
readonly McpPush: "macf.mcp.push";
|
|
12
|
+
readonly TmuxWakeDeliver: "macf.tmux_wake.deliver";
|
|
13
|
+
readonly CertsVerifyChallenge: "macf.certs.verify_challenge";
|
|
14
|
+
readonly CertsSign: "macf.certs.sign";
|
|
15
|
+
};
|
|
16
|
+
/** MACF-specific attribute keys (not covered by OTEL semconv). */
|
|
17
|
+
export declare const Attr: {
|
|
18
|
+
readonly NotifyType: "macf.notify.type";
|
|
19
|
+
readonly IssueNumber: "macf.issue.number";
|
|
20
|
+
readonly AgentRole: "macf.agent.role";
|
|
21
|
+
readonly RemoteCn: "macf.remote_cn";
|
|
22
|
+
readonly TmuxTarget: "macf.tmux.target";
|
|
23
|
+
readonly WakeOutcome: "macf.wake.outcome";
|
|
24
|
+
};
|
|
25
|
+
/** GenAI semconv keys (experimental in v1.36+). */
|
|
26
|
+
export declare const GenAiAttr: {
|
|
27
|
+
readonly AgentName: "gen_ai.agent.name";
|
|
28
|
+
readonly AgentId: "gen_ai.agent.id";
|
|
29
|
+
readonly OperationName: "gen_ai.operation.name";
|
|
30
|
+
readonly System: "gen_ai.system";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Map NotifyPayload.type to the experimental GenAI `operation.name`
|
|
34
|
+
* vocabulary. If the type isn't one we've classified, returns
|
|
35
|
+
* `'notify'` as a catch-all so the span still has a reasonable value.
|
|
36
|
+
*/
|
|
37
|
+
export declare function operationNameForNotifyType(type: NotifyPayload['type']): string;
|
|
38
|
+
//# sourceMappingURL=tracing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,gEAAgE;AAChE,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,uDAAuD;AACvD,eAAO,MAAM,SAAS;;;;;;;;;CASZ,CAAC;AAEX,kEAAkE;AAClE,eAAO,MAAM,IAAI;;;;;;;CAOP,CAAC;AAEX,mDAAmD;AACnD,eAAO,MAAM,SAAS;;;;;CAKZ,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAU9E"}
|
package/dist/tracing.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tracer + attribute conventions for MACF instrumentation.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for span names + attribute keys so the
|
|
5
|
+
* emission layer (server.ts, tmux-wake.ts, mcp.ts) stays decoupled
|
|
6
|
+
* from the SDK import sprawl. Also the only place that knows which
|
|
7
|
+
* `gen_ai.operation.name` value maps to which NotifyPayload type.
|
|
8
|
+
*
|
|
9
|
+
* See DR-021 + macf#194 for the full convention. TL;DR:
|
|
10
|
+
*
|
|
11
|
+
* - `gen_ai.*` attributes follow the experimental v1.36+ OTEL
|
|
12
|
+
* GenAI agent-spans semconv. Subject to rename when semconv
|
|
13
|
+
* stabilizes; keep in one place so the mass-rename is cheap.
|
|
14
|
+
* - `macf.*` attributes are MACF-specific and not covered by any
|
|
15
|
+
* semconv. Prefixed to avoid future collisions.
|
|
16
|
+
* - Span kinds: SERVER for HTTP-handler spans, INTERNAL for
|
|
17
|
+
* in-process operations (MCP push, tmux wake), CLIENT for
|
|
18
|
+
* outgoing HTTPS calls (none today — channel server is a sink).
|
|
19
|
+
*/
|
|
20
|
+
import { trace } from '@opentelemetry/api';
|
|
21
|
+
/** The single tracer instance used across the MACF codebase. */
|
|
22
|
+
export function getTracer() {
|
|
23
|
+
return trace.getTracer('macf');
|
|
24
|
+
}
|
|
25
|
+
/** Span names — centralized for easy rename + grep. */
|
|
26
|
+
export const SpanNames = {
|
|
27
|
+
NotifyReceived: 'macf.server.notify_received',
|
|
28
|
+
SignCsr: 'macf.server.sign_csr',
|
|
29
|
+
StartupRegister: 'macf.server.register',
|
|
30
|
+
StartupCollisionCheck: 'macf.server.collision_check',
|
|
31
|
+
McpPush: 'macf.mcp.push',
|
|
32
|
+
TmuxWakeDeliver: 'macf.tmux_wake.deliver',
|
|
33
|
+
CertsVerifyChallenge: 'macf.certs.verify_challenge',
|
|
34
|
+
CertsSign: 'macf.certs.sign',
|
|
35
|
+
};
|
|
36
|
+
/** MACF-specific attribute keys (not covered by OTEL semconv). */
|
|
37
|
+
export const Attr = {
|
|
38
|
+
NotifyType: 'macf.notify.type',
|
|
39
|
+
IssueNumber: 'macf.issue.number',
|
|
40
|
+
AgentRole: 'macf.agent.role',
|
|
41
|
+
RemoteCn: 'macf.remote_cn',
|
|
42
|
+
TmuxTarget: 'macf.tmux.target',
|
|
43
|
+
WakeOutcome: 'macf.wake.outcome',
|
|
44
|
+
};
|
|
45
|
+
/** GenAI semconv keys (experimental in v1.36+). */
|
|
46
|
+
export const GenAiAttr = {
|
|
47
|
+
AgentName: 'gen_ai.agent.name',
|
|
48
|
+
AgentId: 'gen_ai.agent.id',
|
|
49
|
+
OperationName: 'gen_ai.operation.name',
|
|
50
|
+
System: 'gen_ai.system',
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Map NotifyPayload.type to the experimental GenAI `operation.name`
|
|
54
|
+
* vocabulary. If the type isn't one we've classified, returns
|
|
55
|
+
* `'notify'` as a catch-all so the span still has a reasonable value.
|
|
56
|
+
*/
|
|
57
|
+
export function operationNameForNotifyType(type) {
|
|
58
|
+
switch (type) {
|
|
59
|
+
case 'mention':
|
|
60
|
+
return 'invoke_agent';
|
|
61
|
+
case 'issue_routed':
|
|
62
|
+
return 'handoff';
|
|
63
|
+
case 'startup_check':
|
|
64
|
+
case 'ci_completion':
|
|
65
|
+
return 'notify';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=tracing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAI3C,gEAAgE;AAChE,MAAM,UAAU,SAAS;IACvB,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,uDAAuD;AACvD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,cAAc,EAAE,6BAA6B;IAC7C,OAAO,EAAE,sBAAsB;IAC/B,eAAe,EAAE,sBAAsB;IACvC,qBAAqB,EAAE,6BAA6B;IACpD,OAAO,EAAE,eAAe;IACxB,eAAe,EAAE,wBAAwB;IACzC,oBAAoB,EAAE,6BAA6B;IACnD,SAAS,EAAE,iBAAiB;CACpB,CAAC;AAEX,kEAAkE;AAClE,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,UAAU,EAAE,kBAAkB;IAC9B,WAAW,EAAE,mBAAmB;IAChC,SAAS,EAAE,iBAAiB;IAC5B,QAAQ,EAAE,gBAAgB;IAC1B,UAAU,EAAE,kBAAkB;IAC9B,WAAW,EAAE,mBAAmB;CACxB,CAAC;AAEX,mDAAmD;AACnD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,mBAAmB;IAC9B,OAAO,EAAE,iBAAiB;IAC1B,aAAa,EAAE,uBAAuB;IACtC,MAAM,EAAE,eAAe;CACf,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAA2B;IACpE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,cAAc,CAAC;QACxB,KAAK,cAAc;YACjB,OAAO,SAAS,CAAC;QACnB,KAAK,eAAe,CAAC;QACrB,KAAK,eAAe;YAClB,OAAO,QAAQ,CAAC;IACpB,CAAC;AACH,CAAC"}
|