@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Agent Coordination Framework (MACF)
|
|
3
|
+
*
|
|
4
|
+
* Coordinates multiple Claude Code agents via GitHub,
|
|
5
|
+
* using MCP channels (HTTP/mTLS) for communication.
|
|
6
|
+
*/
|
|
7
|
+
export { MacfError, ConfigError, McpChannelError, HttpsServerError, PortUnavailableError, PortExhaustedError, ValidationError } from '@groundnuty/macf-core';
|
|
8
|
+
export { createMcpChannel } from './mcp.js';
|
|
9
|
+
export { createHttpsServer } from './https.js';
|
|
10
|
+
export { createHealthState } from './health.js';
|
|
11
|
+
export { createLogger } from '@groundnuty/macf-core';
|
|
12
|
+
export { loadConfig } from '@groundnuty/macf-core';
|
|
13
|
+
export { NotifyPayloadSchema, NotifyTypeSchema, HealthResponseSchema, CiCompletionPayloadSchema, CheckSuiteConclusionSchema, } from '@groundnuty/macf-core';
|
|
14
|
+
// P2: Registry & Discovery
|
|
15
|
+
export { createRegistryFromConfig, createRegistry, createGitHubClient, GitHubApiError, AgentInfoSchema, RegistryConfigSchema } from '@groundnuty/macf-core';
|
|
16
|
+
export { checkCollision, CollisionError } from './collision.js';
|
|
17
|
+
export { registerShutdownHandler } from './shutdown.js';
|
|
18
|
+
export { generateToken } from '@groundnuty/macf-core';
|
|
19
|
+
export { checkPendingIssues } from './startup-issues.js';
|
|
20
|
+
// P3: Certificate Management
|
|
21
|
+
export { createCA, backupCAKey, recoverCAKey, encryptCAKey, decryptCAKey, loadCA, CaError } from '@groundnuty/macf-core';
|
|
22
|
+
export { generateAgentCert, generateCSR, signCSR, AgentCertError } from '@groundnuty/macf-core';
|
|
23
|
+
export { createChallenge, verifyAndConsumeChallenge, ChallengeError } from '@groundnuty/macf-core';
|
|
24
|
+
export { SignRequestSchema, SignChallengeResponseSchema, SignCertResponseSchema } from '@groundnuty/macf-core';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7J,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,mBAAmB,EAAE,gBAAgB,EAAE,oBAAoB,EAC3D,yBAAyB,EAAE,0BAA0B,GACtD,MAAM,uBAAuB,CAAC;AAO/B,2BAA2B;AAC3B,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE5J,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,6BAA6B;AAC7B,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEzH,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAaxD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC,GAAG,UAAU,CA4Cb"}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { McpChannelError } from '@groundnuty/macf-core';
|
|
4
|
+
const CHANNEL_INSTRUCTIONS = `Events arrive as <channel source="macf-agent" type="..." ...>.
|
|
5
|
+
|
|
6
|
+
type="issue_routed": A GitHub issue was routed to you by the agent-router Action.
|
|
7
|
+
Read the issue and work on it following your agent identity rules.
|
|
8
|
+
|
|
9
|
+
type="mention": You were @mentioned in an issue comment or PR review.
|
|
10
|
+
Read the context and respond.
|
|
11
|
+
|
|
12
|
+
type="startup_check": Pending issues found at session startup.
|
|
13
|
+
Review and pick up the most important one.`;
|
|
14
|
+
export function createMcpChannel(config) {
|
|
15
|
+
const instructions = config.instructions ?? CHANNEL_INSTRUCTIONS;
|
|
16
|
+
const server = new Server({ name: `macf-${config.agentName}`, version: '0.2.0-rc.0' }, {
|
|
17
|
+
capabilities: {
|
|
18
|
+
experimental: { 'claude/channel': {} },
|
|
19
|
+
},
|
|
20
|
+
instructions,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
async connect() {
|
|
24
|
+
const transport = new StdioServerTransport();
|
|
25
|
+
await server.connect(transport);
|
|
26
|
+
},
|
|
27
|
+
async pushNotification(content, meta) {
|
|
28
|
+
try {
|
|
29
|
+
// notifications/claude/channel is a Claude Code extension not in MCP's
|
|
30
|
+
// typed ServerNotification union, but Server.assertNotificationCapability
|
|
31
|
+
// has no default case so unknown methods pass through at runtime.
|
|
32
|
+
await server.notification({
|
|
33
|
+
method: 'notifications/claude/channel',
|
|
34
|
+
params: { content, meta },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw new McpChannelError(`Failed to push notification: ${err instanceof Error ? err.message : String(err)}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,MAAM,oBAAoB,GAAG;;;;;;;;;6CASgB,CAAC;AAE9C,MAAM,UAAU,gBAAgB,CAAC,MAGhC;IACC,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,oBAAoB,CAAC;IAEjE,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,QAAQ,MAAM,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAC3D;QACE,YAAY,EAAE;YACZ,YAAY,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACvC;QACD,YAAY;KACb,CACF,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,OAAO;YACX,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;YAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAClC,CAAC;QAED,KAAK,CAAC,gBAAgB,CACpB,OAAe,EACf,IAA4B;YAE5B,IAAI,CAAC;gBACH,uEAAuE;gBACvE,0EAA0E;gBAC1E,kEAAkE;gBAClE,MAAO,MAAM,CAAC,YAMM,CAAC;oBACnB,MAAM,EAAE,8BAA8B;oBACtC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;iBAC1B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,eAAe,CACvB,gCAAgC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACnF,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NotifyPayload } from '@groundnuty/macf-core';
|
|
2
|
+
/**
|
|
3
|
+
* Render a NotifyPayload to the operator-facing content string that
|
|
4
|
+
* gets pushed through MCP. Pure function — extracted from `onNotify`
|
|
5
|
+
* in `server.ts` per ultrareview finding A6. Previously the renderer
|
|
6
|
+
* lived inside a 58-line closure that also did logging, health-state
|
|
7
|
+
* mutation, and MCP push; adding a new `NotifyPayload` variant meant
|
|
8
|
+
* editing that closure. Extracting makes variant dispatch testable
|
|
9
|
+
* in isolation.
|
|
10
|
+
*
|
|
11
|
+
* Returns the rendered content and the issue number the payload is
|
|
12
|
+
* associated with (for health-state updates at the call site).
|
|
13
|
+
*/
|
|
14
|
+
export interface FormattedNotify {
|
|
15
|
+
readonly content: string;
|
|
16
|
+
readonly issueNumber?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function formatNotifyContent(payload: NotifyPayload): FormattedNotify;
|
|
19
|
+
//# sourceMappingURL=notify-formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify-formatter.d.ts","sourceRoot":"","sources":["../src/notify-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,eAAe,CA4C3E"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function formatNotifyContent(payload) {
|
|
2
|
+
if (payload.type === 'issue_routed') {
|
|
3
|
+
if (payload.issue_number !== undefined) {
|
|
4
|
+
const suffix = payload.title ? `: ${payload.title}` : '';
|
|
5
|
+
return {
|
|
6
|
+
content: `Issue #${payload.issue_number} was routed to you${suffix}`,
|
|
7
|
+
issueNumber: payload.issue_number,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
content: payload.title
|
|
12
|
+
? `An issue was routed to you: ${payload.title}`
|
|
13
|
+
: 'An issue was routed to you',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (payload.type === 'mention') {
|
|
17
|
+
return { content: payload.message ?? 'You were mentioned' };
|
|
18
|
+
}
|
|
19
|
+
if (payload.type === 'ci_completion') {
|
|
20
|
+
// Prefer the prebuilt `message` (producer has all context) but
|
|
21
|
+
// fall back to a shape-derived rendering if absent. Producers
|
|
22
|
+
// that use CiCompletionPayloadSchema always provide `message`.
|
|
23
|
+
if (payload.message) {
|
|
24
|
+
return { content: payload.message };
|
|
25
|
+
}
|
|
26
|
+
if (payload.pr_number !== undefined && payload.conclusion !== undefined) {
|
|
27
|
+
const prRef = `PR #${payload.pr_number}`;
|
|
28
|
+
if (payload.conclusion === 'success') {
|
|
29
|
+
return { content: `${prRef}: CI SUCCESS` };
|
|
30
|
+
}
|
|
31
|
+
const failing = payload.failing_check_name
|
|
32
|
+
? ` (first failing check: '${payload.failing_check_name}')`
|
|
33
|
+
: '';
|
|
34
|
+
return {
|
|
35
|
+
content: `${prRef}: CI ${payload.conclusion.toUpperCase()}${failing}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return { content: 'CI completed' };
|
|
39
|
+
}
|
|
40
|
+
// startup_check or any future variant falls through here
|
|
41
|
+
return { content: payload.message ?? 'Pending issues found at startup' };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=notify-formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify-formatter.js","sourceRoot":"","sources":["../src/notify-formatter.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,IAAI,OAAO,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACpC,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE,UAAU,OAAO,CAAC,YAAY,qBAAqB,MAAM,EAAE;gBACpE,WAAW,EAAE,OAAO,CAAC,YAAY;aAClC,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,KAAK;gBACpB,CAAC,CAAC,+BAA+B,OAAO,CAAC,KAAK,EAAE;gBAChD,CAAC,CAAC,4BAA4B;SACjC,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,oBAAoB,EAAE,CAAC;IAC9D,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QACrC,+DAA+D;QAC/D,8DAA8D;QAC9D,+DAA+D;QAC/D,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACxE,MAAM,KAAK,GAAG,OAAO,OAAO,CAAC,SAAS,EAAE,CAAC;YACzC,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACrC,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,cAAc,EAAE,CAAC;YAC7C,CAAC;YACD,MAAM,OAAO,GAAG,OAAO,CAAC,kBAAkB;gBACxC,CAAC,CAAC,2BAA2B,OAAO,CAAC,kBAAkB,IAAI;gBAC3D,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,EAAE,GAAG,KAAK,QAAQ,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,GAAG,OAAO,EAAE;aACtE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IACrC,CAAC;IAED,yDAAyD;IACzD,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,iCAAiC,EAAE,CAAC;AAC3E,CAAC"}
|
package/dist/otel.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry bootstrap — dynamic-import gated by env.
|
|
3
|
+
*
|
|
4
|
+
* Zero-cost default (macf#196, revisiting macf#194):
|
|
5
|
+
*
|
|
6
|
+
* The original v0.1.7 shipped this module with TOP-LEVEL static
|
|
7
|
+
* imports of the 5 SDK packages. That violated the zero-cost
|
|
8
|
+
* doctrine structurally: Node resolved + loaded all of them at
|
|
9
|
+
* startup regardless of whether `OTEL_EXPORTER_OTLP_ENDPOINT`
|
|
10
|
+
* was set. Worse, when a consumer workspace didn't have the
|
|
11
|
+
* packages in its `node_modules/` (which was the default — they
|
|
12
|
+
* weren't declared in `plugin/package.json`), the server crashed
|
|
13
|
+
* with `ERR_MODULE_NOT_FOUND` before the env-guard ever ran.
|
|
14
|
+
*
|
|
15
|
+
* v0.1.8 fix: `bootstrapOtel()` is async; inside, we `await import()`
|
|
16
|
+
* the SDK packages only when the endpoint is set. Node only
|
|
17
|
+
* resolves the packages when the operator opts in. If they're
|
|
18
|
+
* missing at that point (operator set the env but forgot `npm
|
|
19
|
+
* install`), we fail LOUD with an actionable message — silent
|
|
20
|
+
* no-op would hide the opt-in attempt.
|
|
21
|
+
*
|
|
22
|
+
* `@opentelemetry/api` stays statically imported because other
|
|
23
|
+
* modules (`src/tracing.ts`, `src/https.ts`) import it at eval
|
|
24
|
+
* time for the no-op tracer path. The `api` package has zero
|
|
25
|
+
* deps and is already a transitive dep, so it's safe to require.
|
|
26
|
+
*
|
|
27
|
+
* Why manual-only (no `auto-instrumentations-node`):
|
|
28
|
+
* Auto-instrumentations monkey-patch core Node modules including
|
|
29
|
+
* HTTPS — and we rely on exact mTLS client-cert validation
|
|
30
|
+
* semantics in `src/https.ts`. Any patching layer between us and
|
|
31
|
+
* Node's TLS code is a correctness risk we don't need.
|
|
32
|
+
*
|
|
33
|
+
* Version pinning:
|
|
34
|
+
* SDK-node packages (`@opentelemetry/sdk-trace-node` etc.) are
|
|
35
|
+
* still pre-1.0 (0.x). Breaking changes land in minor releases.
|
|
36
|
+
* Pin exact versions via package.json — do NOT use caret ranges.
|
|
37
|
+
*
|
|
38
|
+
* See DR-021 for the full rationale + option analysis.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Opt-in OTEL bootstrap. No-op (and zero module-resolution cost) when
|
|
42
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` is unset. When set, dynamic-imports
|
|
43
|
+
* the SDK packages, configures an OTLP-proto exporter, registers the
|
|
44
|
+
* provider globally, and wires SIGTERM/SIGINT span-flush.
|
|
45
|
+
*
|
|
46
|
+
* **Must be awaited before any span-emitting module's handler runs.**
|
|
47
|
+
* In `src/server.ts`, `await bootstrapOtel()` sits at the top of
|
|
48
|
+
* `main()` so the global tracer provider is live before /notify,
|
|
49
|
+
* /sign, etc. start accepting requests.
|
|
50
|
+
*
|
|
51
|
+
* Fail-loud policy:
|
|
52
|
+
* - `OTEL_EXPORTER_OTLP_ENDPOINT` unset → silent return (zero-cost default).
|
|
53
|
+
* - Env set + import fails → process.exit(1) with actionable
|
|
54
|
+
* stderr. Operator explicitly opted into observability; silent
|
|
55
|
+
* no-op would hide the config mistake.
|
|
56
|
+
* - Env set + import ok + provider setup fails → same exit(1).
|
|
57
|
+
*/
|
|
58
|
+
export declare function bootstrapOtel(): Promise<void>;
|
|
59
|
+
//# sourceMappingURL=otel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAsEnD"}
|
package/dist/otel.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry bootstrap — dynamic-import gated by env.
|
|
3
|
+
*
|
|
4
|
+
* Zero-cost default (macf#196, revisiting macf#194):
|
|
5
|
+
*
|
|
6
|
+
* The original v0.1.7 shipped this module with TOP-LEVEL static
|
|
7
|
+
* imports of the 5 SDK packages. That violated the zero-cost
|
|
8
|
+
* doctrine structurally: Node resolved + loaded all of them at
|
|
9
|
+
* startup regardless of whether `OTEL_EXPORTER_OTLP_ENDPOINT`
|
|
10
|
+
* was set. Worse, when a consumer workspace didn't have the
|
|
11
|
+
* packages in its `node_modules/` (which was the default — they
|
|
12
|
+
* weren't declared in `plugin/package.json`), the server crashed
|
|
13
|
+
* with `ERR_MODULE_NOT_FOUND` before the env-guard ever ran.
|
|
14
|
+
*
|
|
15
|
+
* v0.1.8 fix: `bootstrapOtel()` is async; inside, we `await import()`
|
|
16
|
+
* the SDK packages only when the endpoint is set. Node only
|
|
17
|
+
* resolves the packages when the operator opts in. If they're
|
|
18
|
+
* missing at that point (operator set the env but forgot `npm
|
|
19
|
+
* install`), we fail LOUD with an actionable message — silent
|
|
20
|
+
* no-op would hide the opt-in attempt.
|
|
21
|
+
*
|
|
22
|
+
* `@opentelemetry/api` stays statically imported because other
|
|
23
|
+
* modules (`src/tracing.ts`, `src/https.ts`) import it at eval
|
|
24
|
+
* time for the no-op tracer path. The `api` package has zero
|
|
25
|
+
* deps and is already a transitive dep, so it's safe to require.
|
|
26
|
+
*
|
|
27
|
+
* Why manual-only (no `auto-instrumentations-node`):
|
|
28
|
+
* Auto-instrumentations monkey-patch core Node modules including
|
|
29
|
+
* HTTPS — and we rely on exact mTLS client-cert validation
|
|
30
|
+
* semantics in `src/https.ts`. Any patching layer between us and
|
|
31
|
+
* Node's TLS code is a correctness risk we don't need.
|
|
32
|
+
*
|
|
33
|
+
* Version pinning:
|
|
34
|
+
* SDK-node packages (`@opentelemetry/sdk-trace-node` etc.) are
|
|
35
|
+
* still pre-1.0 (0.x). Breaking changes land in minor releases.
|
|
36
|
+
* Pin exact versions via package.json — do NOT use caret ranges.
|
|
37
|
+
*
|
|
38
|
+
* See DR-021 for the full rationale + option analysis.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Opt-in OTEL bootstrap. No-op (and zero module-resolution cost) when
|
|
42
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` is unset. When set, dynamic-imports
|
|
43
|
+
* the SDK packages, configures an OTLP-proto exporter, registers the
|
|
44
|
+
* provider globally, and wires SIGTERM/SIGINT span-flush.
|
|
45
|
+
*
|
|
46
|
+
* **Must be awaited before any span-emitting module's handler runs.**
|
|
47
|
+
* In `src/server.ts`, `await bootstrapOtel()` sits at the top of
|
|
48
|
+
* `main()` so the global tracer provider is live before /notify,
|
|
49
|
+
* /sign, etc. start accepting requests.
|
|
50
|
+
*
|
|
51
|
+
* Fail-loud policy:
|
|
52
|
+
* - `OTEL_EXPORTER_OTLP_ENDPOINT` unset → silent return (zero-cost default).
|
|
53
|
+
* - Env set + import fails → process.exit(1) with actionable
|
|
54
|
+
* stderr. Operator explicitly opted into observability; silent
|
|
55
|
+
* no-op would hide the config mistake.
|
|
56
|
+
* - Env set + import ok + provider setup fails → same exit(1).
|
|
57
|
+
*/
|
|
58
|
+
export async function bootstrapOtel() {
|
|
59
|
+
const endpoint = process.env['OTEL_EXPORTER_OTLP_ENDPOINT'];
|
|
60
|
+
if (endpoint === undefined || endpoint === '')
|
|
61
|
+
return;
|
|
62
|
+
const serviceVersion = process.env['MACF_VERSION'] ?? '0.0.0';
|
|
63
|
+
const serviceName = process.env['OTEL_SERVICE_NAME'] ?? 'macf';
|
|
64
|
+
// Dynamic imports — only resolved when opted in. Lets a workspace
|
|
65
|
+
// WITHOUT `@opentelemetry/sdk-*` in node_modules start cleanly
|
|
66
|
+
// (as long as it doesn't opt in). See macf#196 for the bug this
|
|
67
|
+
// closes; consumer plugin workspaces have only a subset of deps
|
|
68
|
+
// available via `npm install` unless opted into observability.
|
|
69
|
+
let sdkNode;
|
|
70
|
+
let sdkBase;
|
|
71
|
+
let exporter;
|
|
72
|
+
let resources;
|
|
73
|
+
let semconv;
|
|
74
|
+
try {
|
|
75
|
+
[sdkNode, sdkBase, exporter, resources, semconv] = await Promise.all([
|
|
76
|
+
import('@opentelemetry/sdk-trace-node'),
|
|
77
|
+
import('@opentelemetry/sdk-trace-base'),
|
|
78
|
+
import('@opentelemetry/exporter-trace-otlp-proto'),
|
|
79
|
+
import('@opentelemetry/resources'),
|
|
80
|
+
import('@opentelemetry/semantic-conventions'),
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
85
|
+
process.stderr.write(`FATAL: OTEL_EXPORTER_OTLP_ENDPOINT is set ("${endpoint}") but required @opentelemetry/* packages are missing.\n` +
|
|
86
|
+
` Underlying error: ${msg}\n` +
|
|
87
|
+
` Fix: install them in the plugin dir (e.g. run macf-agent's SessionStart npm-install hook), ` +
|
|
88
|
+
`or unset OTEL_EXPORTER_OTLP_ENDPOINT to disable telemetry.\n`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
// Resource attributes inherit from the SDK's default detectors
|
|
92
|
+
// (process.*, host.*, telemetry.sdk.*) via `defaultResource()`,
|
|
93
|
+
// merged with our explicit service.* attributes on top.
|
|
94
|
+
const resource = resources.defaultResource().merge(resources.resourceFromAttributes({
|
|
95
|
+
[semconv.ATTR_SERVICE_NAME]: serviceName,
|
|
96
|
+
[semconv.ATTR_SERVICE_VERSION]: serviceVersion,
|
|
97
|
+
}));
|
|
98
|
+
const provider = new sdkNode.NodeTracerProvider({
|
|
99
|
+
resource,
|
|
100
|
+
spanProcessors: [new sdkBase.BatchSpanProcessor(new exporter.OTLPTraceExporter())],
|
|
101
|
+
});
|
|
102
|
+
// register() installs the provider as global + sets W3C trace-
|
|
103
|
+
// context propagator as default. After this call,
|
|
104
|
+
// `trace.getTracer('macf')` from anywhere in the process returns a
|
|
105
|
+
// recording tracer bound to this provider.
|
|
106
|
+
provider.register();
|
|
107
|
+
// Clean shutdown: flush queued spans before process exits. Without
|
|
108
|
+
// these handlers, in-flight batches are dropped on SIGTERM/SIGINT —
|
|
109
|
+
// the last few seconds of coordination events never reach the
|
|
110
|
+
// collector.
|
|
111
|
+
const shutdown = async () => {
|
|
112
|
+
try {
|
|
113
|
+
await provider.shutdown();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Silent — we're exiting regardless; don't spam stderr.
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
process.once('SIGTERM', shutdown);
|
|
120
|
+
process.once('SIGINT', shutdown);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=otel.js.map
|
package/dist/otel.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.js","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC5D,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO;IAEtD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,OAAO,CAAC;IAC9D,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,MAAM,CAAC;IAE/D,kEAAkE;IAClE,+DAA+D;IAC/D,gEAAgE;IAChE,gEAAgE;IAChE,+DAA+D;IAC/D,IAAI,OAAuD,CAAC;IAC5D,IAAI,OAAuD,CAAC;IAC5D,IAAI,QAAmE,CAAC;IACxE,IAAI,SAAoD,CAAC;IACzD,IAAI,OAA6D,CAAC;IAClE,IAAI,CAAC;QACH,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACnE,MAAM,CAAC,+BAA+B,CAAC;YACvC,MAAM,CAAC,+BAA+B,CAAC;YACvC,MAAM,CAAC,0CAA0C,CAAC;YAClD,MAAM,CAAC,0BAA0B,CAAC;YAClC,MAAM,CAAC,qCAAqC,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+CAA+C,QAAQ,0DAA0D;YAC/G,uBAAuB,GAAG,IAAI;YAC9B,+FAA+F;YAC/F,8DAA8D,CACjE,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,+DAA+D;IAC/D,gEAAgE;IAChE,wDAAwD;IACxD,MAAM,QAAQ,GAAG,SAAS,CAAC,eAAe,EAAE,CAAC,KAAK,CAChD,SAAS,CAAC,sBAAsB,CAAC;QAC/B,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,WAAW;QACxC,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,cAAc;KAC/C,CAAC,CACH,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAC,kBAAkB,CAAC;QAC9C,QAAQ;QACR,cAAc,EAAE,CAAC,IAAI,OAAO,CAAC,kBAAkB,CAAC,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAC;KACnF,CAAC,CAAC;IAEH,+DAA+D;IAC/D,kDAAkD;IAClD,mEAAmE;IACnE,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAEpB,mEAAmE;IACnE,oEAAoE;IACpE,8DAA8D;IAC9D,aAAa;IACb,MAAM,QAAQ,GAAG,KAAK,IAAmB,EAAE;QACzC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;IACH,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// macf#196: OTEL bootstrap is now async + dynamic. We still import
|
|
2
|
+
// the module eagerly (to get the function export), but the actual
|
|
3
|
+
// SDK packages are loaded only when the env is set, inside
|
|
4
|
+
// `bootstrapOtel()`. Calls to `trace.getTracer()` before the bootstrap
|
|
5
|
+
// runs return the global no-op tracer — harmless, since no spans are
|
|
6
|
+
// created before main() awaits the bootstrap.
|
|
7
|
+
import { bootstrapOtel } from './otel.js';
|
|
8
|
+
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
9
|
+
import { getTracer, SpanNames } from './tracing.js';
|
|
10
|
+
import { loadConfig } from '@groundnuty/macf-core';
|
|
11
|
+
import { createLogger } from '@groundnuty/macf-core';
|
|
12
|
+
import { createMcpChannel } from './mcp.js';
|
|
13
|
+
import { createHealthState } from './health.js';
|
|
14
|
+
import { createHttpsServer } from './https.js';
|
|
15
|
+
import { createRegistryFromConfig } from '@groundnuty/macf-core';
|
|
16
|
+
import { checkCollision, CollisionError } from './collision.js';
|
|
17
|
+
import { registerShutdownHandler } from './shutdown.js';
|
|
18
|
+
import { generateToken } from '@groundnuty/macf-core';
|
|
19
|
+
import { createChallenge, verifyAndConsumeChallenge } from '@groundnuty/macf-core';
|
|
20
|
+
import { createChallengeStore } from '@groundnuty/macf-core';
|
|
21
|
+
import { signCSR } from '@groundnuty/macf-core';
|
|
22
|
+
import { loadCA } from '@groundnuty/macf-core';
|
|
23
|
+
import { HttpError } from '@groundnuty/macf-core';
|
|
24
|
+
import { formatNotifyContent } from './notify-formatter.js';
|
|
25
|
+
import { wakeViaTmux } from './tmux-wake.js';
|
|
26
|
+
async function main() {
|
|
27
|
+
// Bootstrap OTEL BEFORE anything calls `trace.getTracer()` with
|
|
28
|
+
// intent to record. Function is no-op when
|
|
29
|
+
// OTEL_EXPORTER_OTLP_ENDPOINT is unset; when set, dynamic-imports
|
|
30
|
+
// the SDK packages + registers the global provider. See macf#196.
|
|
31
|
+
await bootstrapOtel();
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
const logger = createLogger({
|
|
34
|
+
logPath: config.logPath,
|
|
35
|
+
debug: config.debug,
|
|
36
|
+
});
|
|
37
|
+
// Partial-startup failures (MCP connected, port bound, then registry
|
|
38
|
+
// or collision fails) would otherwise crash with only the stderr
|
|
39
|
+
// message from the outer catch — channel.log would show the agent
|
|
40
|
+
// starting and then go silent, leaving operators with no signal.
|
|
41
|
+
// Wrap the startup body so post-logger failures land in the log.
|
|
42
|
+
// Ultrareview finding H5.
|
|
43
|
+
try {
|
|
44
|
+
await runStartup();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.error('startup_failed', {
|
|
48
|
+
error: err instanceof Error ? err.message : String(err),
|
|
49
|
+
code: err.code ?? 'unknown',
|
|
50
|
+
});
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
async function runStartup() {
|
|
54
|
+
const mcp = createMcpChannel({ agentName: config.agentName });
|
|
55
|
+
const health = createHealthState(config.agentName, config.agentType);
|
|
56
|
+
const onNotify = async (payload) => {
|
|
57
|
+
const meta = { type: payload.type };
|
|
58
|
+
if (payload.issue_number !== undefined) {
|
|
59
|
+
meta['issue_number'] = String(payload.issue_number);
|
|
60
|
+
}
|
|
61
|
+
if (payload.source !== undefined) {
|
|
62
|
+
meta['source'] = payload.source;
|
|
63
|
+
}
|
|
64
|
+
const { content, issueNumber } = formatNotifyContent(payload);
|
|
65
|
+
if (issueNumber !== undefined) {
|
|
66
|
+
health.setCurrentIssue(issueNumber);
|
|
67
|
+
}
|
|
68
|
+
logger.info('notify_received', {
|
|
69
|
+
type: payload.type,
|
|
70
|
+
issue: payload.issue_number,
|
|
71
|
+
});
|
|
72
|
+
// macf#194: wrap MCP push in an INTERNAL child span of the active
|
|
73
|
+
// notify span. Shows up in Langfuse as a timed hop between the
|
|
74
|
+
// inbound HTTP and the tmux wake.
|
|
75
|
+
const tracer = getTracer();
|
|
76
|
+
await tracer.startActiveSpan(SpanNames.McpPush, { kind: SpanKind.INTERNAL }, async (span) => {
|
|
77
|
+
try {
|
|
78
|
+
await mcp.pushNotification(content, meta);
|
|
79
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
span.recordException(err);
|
|
83
|
+
span.setStatus({
|
|
84
|
+
code: SpanStatusCode.ERROR,
|
|
85
|
+
message: err instanceof Error ? err.message : String(err),
|
|
86
|
+
});
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
span.end();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
health.recordNotification();
|
|
94
|
+
logger.info('mcp_pushed', {
|
|
95
|
+
type: payload.type,
|
|
96
|
+
issue: payload.issue_number,
|
|
97
|
+
});
|
|
98
|
+
// macf#185: sidecar wake via tmux-send-to-claude.sh. The MCP push
|
|
99
|
+
// above deposits the notification in the channel-server's
|
|
100
|
+
// observable state but does NOT interrupt a running Claude TUI
|
|
101
|
+
// with a new prompt — /notify ≠ wake without this step. Tmux
|
|
102
|
+
// injection surfaces the notification as the TUI's next input
|
|
103
|
+
// turn, so the agent actually processes it. Fail-silent on any
|
|
104
|
+
// path where tmux isn't available (no workspace dir, no tmux
|
|
105
|
+
// session, helper missing, tmux command errors).
|
|
106
|
+
if (config.workspaceDir !== undefined) {
|
|
107
|
+
// Use the formatted content as the wake prompt — same text
|
|
108
|
+
// Claude would see via the MCP channel, just delivered
|
|
109
|
+
// through the input buffer path so it becomes an actual turn.
|
|
110
|
+
wakeViaTmux(content, {
|
|
111
|
+
workspaceDir: config.workspaceDir,
|
|
112
|
+
session: config.tmuxSession,
|
|
113
|
+
window: config.tmuxWindow,
|
|
114
|
+
logger,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
logger.info('tmux_wake_skipped', {
|
|
119
|
+
reason: 'no_workspace_dir',
|
|
120
|
+
detail: 'MACF_WORKSPACE_DIR unset',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
// P2: Generate token early — needed for /sign endpoint and registry
|
|
125
|
+
const token = await generateToken();
|
|
126
|
+
const registry = createRegistryFromConfig(config.registry, config.project, token);
|
|
127
|
+
const { createGitHubClient } = await import('@groundnuty/macf-core');
|
|
128
|
+
// Build the variables client for the /sign challenge flow
|
|
129
|
+
let signPathPrefix;
|
|
130
|
+
switch (config.registry.type) {
|
|
131
|
+
case 'org':
|
|
132
|
+
signPathPrefix = `/orgs/${config.registry.org}`;
|
|
133
|
+
break;
|
|
134
|
+
case 'profile':
|
|
135
|
+
signPathPrefix = `/repos/${config.registry.user}/${config.registry.user}`;
|
|
136
|
+
break;
|
|
137
|
+
case 'repo':
|
|
138
|
+
signPathPrefix = `/repos/${config.registry.owner}/${config.registry.repo}`;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
const varsClient = createGitHubClient(signPathPrefix, token);
|
|
142
|
+
// In-memory challenge store (DR-010, #80). Process-local; server restart
|
|
143
|
+
// between step 1 and step 2 of a flow invalidates outstanding challenges.
|
|
144
|
+
const challengeStore = createChallengeStore();
|
|
145
|
+
// /sign endpoint handler — two-step challenge-response (DR-010).
|
|
146
|
+
// Step 1: allocate challenge, return id + instruction (no registry write).
|
|
147
|
+
// Step 2: verify challenge_id + registry-observed value, sign CSR.
|
|
148
|
+
const onSign = async (request) => {
|
|
149
|
+
// Try to load CA key — if not available, this agent can't sign.
|
|
150
|
+
let ca;
|
|
151
|
+
try {
|
|
152
|
+
ca = loadCA(config.caCertPath, config.caKeyPath);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
throw new HttpError(503, 'CA key not available on this agent');
|
|
156
|
+
}
|
|
157
|
+
if (!request.challenge_done) {
|
|
158
|
+
// Step 1: allocate in-memory challenge, return id + instruction.
|
|
159
|
+
const challenge = createChallenge({
|
|
160
|
+
project: config.project,
|
|
161
|
+
agentName: request.agent_name,
|
|
162
|
+
store: challengeStore,
|
|
163
|
+
});
|
|
164
|
+
logger.info('sign_challenge_created', {
|
|
165
|
+
agent_name: request.agent_name,
|
|
166
|
+
challenge_id: challenge.challengeId,
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
challenge_id: challenge.challengeId,
|
|
170
|
+
instruction: challenge.instruction,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Step 2: verify challenge + sign CSR. The refine() on SignRequestSchema
|
|
174
|
+
// already guarantees challenge_id is present when challenge_done is true.
|
|
175
|
+
const result = await verifyAndConsumeChallenge({
|
|
176
|
+
project: config.project,
|
|
177
|
+
agentName: request.agent_name,
|
|
178
|
+
challengeId: request.challenge_id,
|
|
179
|
+
store: challengeStore,
|
|
180
|
+
client: varsClient,
|
|
181
|
+
});
|
|
182
|
+
if (result === 'mismatch') {
|
|
183
|
+
// Generic error — do not leak which check failed (no oracle for
|
|
184
|
+
// attackers probing expired/mismatched-agent/wrong-value, etc).
|
|
185
|
+
logger.warn('sign_challenge_failed', { agent_name: request.agent_name });
|
|
186
|
+
throw new HttpError(401, 'challenge verification failed');
|
|
187
|
+
}
|
|
188
|
+
logger.info('sign_challenge_verified', { agent_name: request.agent_name });
|
|
189
|
+
const certPem = await signCSR({
|
|
190
|
+
csrPem: request.csr,
|
|
191
|
+
agentName: request.agent_name,
|
|
192
|
+
caCertPem: ca.certPem,
|
|
193
|
+
caKeyPem: ca.keyPem,
|
|
194
|
+
});
|
|
195
|
+
logger.info('sign_cert_issued', { agent_name: request.agent_name });
|
|
196
|
+
return { cert: certPem };
|
|
197
|
+
};
|
|
198
|
+
const httpsServer = createHttpsServer({
|
|
199
|
+
caCertPath: config.caCertPath,
|
|
200
|
+
agentCertPath: config.agentCertPath,
|
|
201
|
+
agentKeyPath: config.agentKeyPath,
|
|
202
|
+
onNotify,
|
|
203
|
+
onHealth: () => health.getHealth(),
|
|
204
|
+
onSign,
|
|
205
|
+
logger,
|
|
206
|
+
});
|
|
207
|
+
// P1: Connect MCP channel
|
|
208
|
+
await mcp.connect();
|
|
209
|
+
// P1: Bind port
|
|
210
|
+
const { actualPort } = await httpsServer.start(config.port, config.host);
|
|
211
|
+
// P2: Collision detection
|
|
212
|
+
const collisionResult = await checkCollision(config.agentName, registry, {
|
|
213
|
+
caCertPath: config.caCertPath,
|
|
214
|
+
agentCertPath: config.agentCertPath,
|
|
215
|
+
agentKeyPath: config.agentKeyPath,
|
|
216
|
+
}, logger);
|
|
217
|
+
if (collisionResult.action === 'abort') {
|
|
218
|
+
await httpsServer.stop();
|
|
219
|
+
throw new CollisionError(config.agentName, collisionResult.existing.host, collisionResult.existing.port);
|
|
220
|
+
}
|
|
221
|
+
// P2: Register in GitHub variable (use advertiseHost, not bind address)
|
|
222
|
+
const agentInfo = {
|
|
223
|
+
host: config.advertiseHost,
|
|
224
|
+
port: actualPort,
|
|
225
|
+
type: config.agentType,
|
|
226
|
+
instance_id: config.instanceId,
|
|
227
|
+
started: new Date().toISOString(),
|
|
228
|
+
};
|
|
229
|
+
await registry.register(config.agentName, agentInfo);
|
|
230
|
+
logger.info('registered', {
|
|
231
|
+
agent: config.agentName,
|
|
232
|
+
host: config.advertiseHost,
|
|
233
|
+
port: actualPort,
|
|
234
|
+
instance_id: config.instanceId,
|
|
235
|
+
});
|
|
236
|
+
// P2: Register shutdown handler
|
|
237
|
+
registerShutdownHandler({
|
|
238
|
+
agentName: config.agentName,
|
|
239
|
+
registry,
|
|
240
|
+
httpsServer,
|
|
241
|
+
logger,
|
|
242
|
+
});
|
|
243
|
+
logger.info('server_started', {
|
|
244
|
+
port: actualPort,
|
|
245
|
+
host: config.advertiseHost,
|
|
246
|
+
agent: config.agentName,
|
|
247
|
+
type: config.agentType,
|
|
248
|
+
instance_id: config.instanceId,
|
|
249
|
+
});
|
|
250
|
+
} // end runStartup
|
|
251
|
+
}
|
|
252
|
+
main().catch((err) => {
|
|
253
|
+
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
});
|
|
256
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|