ahp-channels 0.1.0-alpha.1

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/PUBLISHING.md +76 -0
  3. package/README.md +96 -0
  4. package/ROADMAP.md +46 -0
  5. package/SHIPPING.md +24 -0
  6. package/dist/ahp.d.ts +17 -0
  7. package/dist/ahp.js +70 -0
  8. package/dist/bridge.d.ts +52 -0
  9. package/dist/bridge.js +343 -0
  10. package/dist/channelPrompt.d.ts +5 -0
  11. package/dist/channelPrompt.js +22 -0
  12. package/dist/channelRuntime.d.ts +76 -0
  13. package/dist/channelRuntime.js +224 -0
  14. package/dist/cli.d.ts +2 -0
  15. package/dist/cli.js +714 -0
  16. package/dist/config.d.ts +39 -0
  17. package/dist/config.js +166 -0
  18. package/dist/daemonClient.d.ts +5 -0
  19. package/dist/daemonClient.js +216 -0
  20. package/dist/daemonMain.d.ts +2 -0
  21. package/dist/daemonMain.js +44 -0
  22. package/dist/daemonPaths.d.ts +8 -0
  23. package/dist/daemonPaths.js +69 -0
  24. package/dist/daemonProtocol.d.ts +134 -0
  25. package/dist/daemonProtocol.js +119 -0
  26. package/dist/daemonServer.d.ts +64 -0
  27. package/dist/daemonServer.js +607 -0
  28. package/dist/endpoints.d.ts +23 -0
  29. package/dist/endpoints.js +159 -0
  30. package/dist/eventJournal.d.ts +23 -0
  31. package/dist/eventJournal.js +190 -0
  32. package/dist/instancePaths.d.ts +4 -0
  33. package/dist/instancePaths.js +23 -0
  34. package/dist/lockedFile.d.ts +2 -0
  35. package/dist/lockedFile.js +39 -0
  36. package/dist/mcpChannel.d.ts +32 -0
  37. package/dist/mcpChannel.js +186 -0
  38. package/dist/plugins.d.ts +32 -0
  39. package/dist/plugins.js +251 -0
  40. package/dist/process.d.ts +6 -0
  41. package/dist/process.js +19 -0
  42. package/dist/secretInput.d.ts +10 -0
  43. package/dist/secretInput.js +118 -0
  44. package/dist/secrets.d.ts +17 -0
  45. package/dist/secrets.js +46 -0
  46. package/dist/socketWebSocketTransport.d.ts +16 -0
  47. package/dist/socketWebSocketTransport.js +162 -0
  48. package/dist/telegramAccess.d.ts +24 -0
  49. package/dist/telegramAccess.js +176 -0
  50. package/dist/version.d.ts +1 -0
  51. package/dist/version.js +1 -0
  52. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tyler Leonhardt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PUBLISHING.md ADDED
@@ -0,0 +1,76 @@
1
+ # Publishing
2
+
3
+ `ahp-channels` uses the same tag-driven npm release model as
4
+ [`TylerLeonhardt/ahpx`](https://github.com/TylerLeonhardt/ahpx).
5
+
6
+ Pushing `vX.Y.Z` runs [publish.yml](./.github/workflows/publish.yml). The
7
+ workflow publishes the version already committed in `package.json`; it never
8
+ bumps versions itself.
9
+
10
+ ## Bootstrap the package once
11
+
12
+ The package must exist on npm before its trusted publisher can be configured.
13
+ From a trusted machine:
14
+
15
+ ```powershell
16
+ npm adduser
17
+ cd C:\path\to\ahp-channels
18
+ npm run check
19
+ npm run test:package
20
+ npm publish --tag next --provenance=false
21
+ ```
22
+
23
+ The local bootstrap disables provenance because it does not run in an OIDC
24
+ environment. All later workflow publishes use trusted publishing and generate
25
+ provenance automatically.
26
+
27
+ Then open npmjs.com → `ahp-channels` → Settings → Trusted Publisher and set:
28
+
29
+ - Provider: GitHub Actions
30
+ - Owner: `TylerLeonhardt`
31
+ - Repository: `ahp-channels`
32
+ - Workflow: `publish.yml`
33
+ - Environment: leave blank
34
+
35
+ After confirming one OIDC release, set Publishing access to **Require 2FA and
36
+ disallow tokens**.
37
+
38
+ ## Cut subsequent releases
39
+
40
+ ```powershell
41
+ # Choose the intended version explicitly.
42
+ npm version prerelease --preid alpha --no-git-tag-version
43
+ # Or: npm version patch|minor|major --no-git-tag-version
44
+
45
+ $version = node -p "require('./package.json').version"
46
+ git add package.json package-lock.json src/version.ts
47
+ git commit -m "Bump version to $version"
48
+ git push origin main
49
+ git tag -a "v$version" -m "v$version"
50
+ git push origin "v$version"
51
+ ```
52
+
53
+ The `npm version` lifecycle synchronizes `src/version.ts`, and the build fails
54
+ if it does not match `package.json`. The package smoke test verifies the CLI and
55
+ tarball versions; the publish workflow separately verifies the git tag.
56
+
57
+ Prerelease versions publish under npm's `next` tag. Stable versions publish
58
+ under `latest`.
59
+
60
+ Watch the release:
61
+
62
+ ```powershell
63
+ gh run watch
64
+ npm view ahp-channels dist-tags --json
65
+ ```
66
+
67
+ ## Pipeline guarantees
68
+
69
+ The workflow:
70
+
71
+ 1. Rejects a tag that differs from `package.json`.
72
+ 2. Runs type checking, unit tests, and the build.
73
+ 3. Installs and exercises the packed npm artifact.
74
+ 4. Rejects versions already present on npm.
75
+ 5. Publishes through npm OIDC trusted publishing.
76
+ 6. Creates a matching GitHub Release.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # ahp-channels
2
+
3
+ Run Claude Code channel plugins against any Agent Host Protocol server.
4
+
5
+ `ahp-channels` is an experimental compatibility adapter. It launches a
6
+ Claude-style MCP channel plugin, forwards inbound channel notifications to an
7
+ AHP chat, and exposes the plugin's MCP tools as AHP client tools.
8
+
9
+ ## Quick start
10
+
11
+ ```powershell
12
+ npm install
13
+ npm run build
14
+
15
+ node .\dist\cli.js plugin install telegram@claude-plugins-official
16
+ node .\dist\cli.js host discover
17
+ node .\dist\cli.js session list
18
+ node .\dist\cli.js channel create telegram --plugin telegram --session <session-uri>
19
+ node .\dist\cli.js channel secret set telegram TELEGRAM_BOT_TOKEN
20
+ node .\dist\cli.js channel start telegram
21
+ ```
22
+
23
+ The CLI stores configuration under `~/.ahp-channels` by default. Override this
24
+ with `AHP_CHANNELS_HOME`.
25
+
26
+ Telegram currently requires Bun, matching the upstream plugin. The adapter
27
+ supports standalone TCP hosts and normal editor Agent Hosts over Windows named
28
+ pipes or Unix domain sockets.
29
+
30
+ DM the bot once it starts, then approve and lock down the sender locally:
31
+
32
+ ```powershell
33
+ ahp-channels channel access status telegram
34
+ ahp-channels channel access pair telegram <code>
35
+ ahp-channels channel access policy telegram allowlist
36
+ ```
37
+
38
+ Secrets are stored in Windows Credential Manager, macOS Keychain, or a
39
+ persistent Linux Secret Service. Named instances receive isolated plugin state under
40
+ `~/.ahp-channels/instances/<name>`.
41
+
42
+ ## Status
43
+
44
+ The compatibility bridge and durable daemon control milestones are complete.
45
+ See [ROADMAP.md](./ROADMAP.md) for the remaining setup, reliability, and
46
+ distribution work.
47
+
48
+ ## Manage a channel
49
+
50
+ ```powershell
51
+ ahp-channels channel status telegram
52
+ ahp-channels channel switch telegram --session <new-session-uri>
53
+ ahp-channels channel stop telegram
54
+ ahp-channels channel start telegram
55
+ ahp-channels channel delete telegram
56
+ ```
57
+
58
+ The daemon starts on demand, remembers desired running channels, and restarts
59
+ them after a daemon or channel-process restart. A switch is rejected while the
60
+ channel is processing a turn, so an in-flight reply is never silently orphaned.
61
+ Inbound events with stable platform IDs are journaled before AHP dispatch and
62
+ deduplicated across process restarts.
63
+
64
+ Deleting a channel also removes its keyring entries and isolated state,
65
+ including allowlists, downloaded attachments, and pending event data.
66
+
67
+ ```powershell
68
+ ahp-channels daemon status
69
+ ahp-channels daemon logs
70
+ ahp-channels daemon stop
71
+ ahp-channels daemon start
72
+ ```
73
+
74
+ Control traffic uses a per-install random token over a local named pipe on
75
+ Windows or a mode-`0600` Unix socket. Configuration writes are atomic and use a
76
+ heartbeat-backed cross-process lock.
77
+
78
+ For one-off foreground use, `channel run` remains available:
79
+
80
+ ```powershell
81
+ ahp-channels channel run telegram --session <session-uri>
82
+ ```
83
+
84
+ ## Development
85
+
86
+ ```powershell
87
+ npm test
88
+ npm run typecheck
89
+ npm run build
90
+ npm run test:package
91
+ npm run e2e:local
92
+ npm run e2e:daemon
93
+ ```
94
+
95
+ See [SHIPPING.md](./SHIPPING.md) for the npm prerelease gates and
96
+ [PUBLISHING.md](./PUBLISHING.md) for the tag-driven release process.
package/ROADMAP.md ADDED
@@ -0,0 +1,46 @@
1
+ # Roadmap
2
+
3
+ ## Completed: Runnable compatibility bridge
4
+
5
+ - Install relative-path plugins from Claude-style Git marketplaces.
6
+ - Discover local VS Code Agent Host endpoints.
7
+ - List sessions on a discovered host.
8
+ - Run one stdio MCP channel against one existing AHP chat.
9
+ - Translate channel notifications and client-owned tool calls.
10
+ - Verify against a live local Agent Host.
11
+
12
+ ## Completed: Durable channel control
13
+
14
+ - Named instances with persistent channel-to-session bindings.
15
+ - Authenticated background daemon with start, stop, status, switch, and logs.
16
+ - Desired-state restoration and bounded exponential restart retries.
17
+ - Busy-session switch protection and failed-switch rollback.
18
+ - Atomic cross-process configuration updates.
19
+
20
+ ## Completed: Alpha reliability gates
21
+
22
+ - Normal editor endpoints over Windows named pipes and Unix sockets.
23
+ - OS-keyring secrets with environment-only injection into channel processes.
24
+ - Named instances with isolated state directories.
25
+ - Durable event IDs, pending replay, and bounded deduplication history.
26
+ - First-class secret and Telegram access commands.
27
+
28
+ ## Next: Operational polish
29
+
30
+ - Log rotation and richer health diagnostics.
31
+ - Stable aliases for explicitly selected remote Agent Hosts.
32
+ - Additional channel-specific configuration profiles.
33
+
34
+ ## Later: Broader compatibility
35
+
36
+ - Permission relay with sanitized previews and expiring request IDs.
37
+ - Git subdirectory, URL, npm, and pip marketplace sources.
38
+ - Virtual plugin projection for channel instructions and management skills.
39
+ - Attachment and resource-reference translation.
40
+
41
+ ## Later: Governance and distribution
42
+
43
+ - Signed or pinned marketplace policy.
44
+ - Runtime-enforced channel and permission-relay allowlists.
45
+ - OS credential-store integrations.
46
+ - Packaged binaries and service installation.
package/SHIPPING.md ADDED
@@ -0,0 +1,24 @@
1
+ # Shipping checklist
2
+
3
+ ## `0.1.0-alpha.1`
4
+
5
+ - [x] Claude-style MCP channel compatibility.
6
+ - [x] TCP, Windows named-pipe, and Unix-socket Agent Host connections.
7
+ - [x] Named instances with daemon start, stop, restart, status, and switch.
8
+ - [x] OS-keyring secrets and isolated instance state.
9
+ - [x] Durable event replay and deduplication.
10
+ - [x] Windows and Linux CI.
11
+ - [x] Installed-package smoke test.
12
+ - [x] Live direct bridge and two-session daemon switch tests.
13
+ - [x] Confirm the `ahp-channels` npm package name is available.
14
+ - [x] Choose and add the repository license (MIT).
15
+ - [x] Add the tag-driven OIDC publish workflow and release documentation.
16
+ - [ ] Bootstrap the package and configure npm trusted publishing for `TylerLeonhardt/ahp-channels`.
17
+ - [ ] Run a fresh-machine Telegram setup on macOS or Linux.
18
+ - [ ] Publish `0.1.0-alpha.1` under the npm `next` tag.
19
+
20
+ ## `0.1.0`
21
+
22
+ - [ ] Soak the alpha with Telegram and fakechat.
23
+ - [ ] Add log rotation and actionable health diagnostics.
24
+ - [ ] Resolve alpha feedback and document compatibility limits.
package/dist/ahp.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { type InitializeResult, type SessionState, type SessionSummary } from '@microsoft/agent-host-protocol';
2
+ import { AhpClient, type Subscription } from '@microsoft/agent-host-protocol/client';
3
+ import type { AgentHostEndpoint } from './endpoints.js';
4
+ export interface ConnectedAgentHost {
5
+ readonly client: AhpClient;
6
+ readonly clientId: string;
7
+ readonly initializeResult: InitializeResult;
8
+ }
9
+ export interface SubscribedSession {
10
+ readonly state: SessionState;
11
+ readonly subscription: Subscription;
12
+ }
13
+ export declare function createChannelClientId(plugin: string, session: string): string;
14
+ export declare function connectAgentHost(endpoint: AgentHostEndpoint, clientId?: string): Promise<ConnectedAgentHost>;
15
+ export declare function listSessions(client: AhpClient): Promise<readonly SessionSummary[]>;
16
+ export declare function subscribeSession(client: AhpClient, session: string): Promise<SubscribedSession>;
17
+ export declare function resolveChat(state: SessionState, requested?: string, session?: string): string;
package/dist/ahp.js ADDED
@@ -0,0 +1,70 @@
1
+ import { SUPPORTED_PROTOCOL_VERSIONS, } from '@microsoft/agent-host-protocol';
2
+ import { AhpClient } from '@microsoft/agent-host-protocol/client';
3
+ import { WebSocketTransport } from '@microsoft/agent-host-protocol/ws';
4
+ import { createHash, randomUUID } from 'node:crypto';
5
+ import { SocketWebSocketTransport } from './socketWebSocketTransport.js';
6
+ export function createChannelClientId(plugin, session) {
7
+ const digest = createHash('sha256')
8
+ .update('ahp-channels\0')
9
+ .update(plugin)
10
+ .update('\0')
11
+ .update(session)
12
+ .digest('hex');
13
+ return `${digest.slice(0, 8)}-${digest.slice(8, 12)}-${digest.slice(12, 16)}-${digest.slice(16, 20)}-${digest.slice(20, 32)}`;
14
+ }
15
+ export async function connectAgentHost(endpoint, clientId = randomUUID()) {
16
+ const transport = endpoint.endpoint.type === 'tcp'
17
+ ? await connectTcp(endpoint)
18
+ : await SocketWebSocketTransport.connect(endpoint.endpoint.path, endpoint.connectionToken);
19
+ const client = new AhpClient(transport);
20
+ client.connect();
21
+ try {
22
+ const initializeResult = await client.initialize({
23
+ clientId,
24
+ protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
25
+ initialSubscriptions: ['ahp-root://'],
26
+ });
27
+ return { client, clientId, initializeResult };
28
+ }
29
+ catch (error) {
30
+ await client.shutdown();
31
+ throw error;
32
+ }
33
+ async function connectTcp(endpoint) {
34
+ if (endpoint.endpoint.type !== 'tcp') {
35
+ throw new Error('Expected a TCP Agent Host endpoint');
36
+ }
37
+ const url = new URL(`ws://${endpoint.endpoint.host}:${endpoint.endpoint.port}/`);
38
+ url.searchParams.set('tkn', endpoint.connectionToken);
39
+ return WebSocketTransport.connect(url);
40
+ }
41
+ }
42
+ export async function listSessions(client) {
43
+ const result = await client.request('listSessions', { channel: 'ahp-root://' });
44
+ return result.items;
45
+ }
46
+ export async function subscribeSession(client, session) {
47
+ const { result, subscription } = await client.subscribe(session);
48
+ if (!result.snapshot) {
49
+ await subscription.close();
50
+ throw new Error(`Agent Host returned no state snapshot for session ${session}`);
51
+ }
52
+ return {
53
+ state: result.snapshot.state,
54
+ subscription,
55
+ };
56
+ }
57
+ export function resolveChat(state, requested, session = 'session') {
58
+ if (requested) {
59
+ const known = state.chats.some(chat => chat.resource === requested);
60
+ if (!known) {
61
+ throw new Error(`Chat ${requested} does not belong to ${session}`);
62
+ }
63
+ return requested;
64
+ }
65
+ const chat = state.defaultChat ?? state.chats[0]?.resource;
66
+ if (!chat) {
67
+ throw new Error(`${session} has no chat`);
68
+ }
69
+ return chat;
70
+ }
@@ -0,0 +1,52 @@
1
+ import { type ChatState, type ChatToolCallReadyAction, type StateAction } from '@microsoft/agent-host-protocol';
2
+ import type { DispatchHandle, SubscriptionEvent } from '@microsoft/agent-host-protocol/client';
3
+ import { type ChannelEventJournal } from './eventJournal.js';
4
+ import type { McpChannelClient, StartedMcpChannel } from './mcpChannel.js';
5
+ export interface ChannelBridgeOptions {
6
+ readonly client: {
7
+ dispatch(channel: string, action: StateAction, clientSeq?: number): DispatchHandle;
8
+ unsubscribe?(channel: string): Promise<void>;
9
+ };
10
+ readonly clientId: string;
11
+ readonly session: string;
12
+ readonly chat: string;
13
+ readonly chatState: ChatState;
14
+ readonly chatSubscription: AsyncIterable<SubscriptionEvent> & {
15
+ close(): Promise<void>;
16
+ };
17
+ readonly channel: Pick<McpChannelClient, 'setChannelHandler' | 'callTool' | 'close'>;
18
+ readonly channelInfo: StartedMcpChannel;
19
+ readonly eventJournal?: ChannelEventJournal;
20
+ readonly autoApproveTools?: boolean;
21
+ readonly onStatus?: (message: string) => void;
22
+ }
23
+ export declare class ChannelBridge {
24
+ private readonly options;
25
+ private activeTurnId;
26
+ private readonly queuedMessageIds;
27
+ private readonly pendingTools;
28
+ private readonly inFlightEventHandlers;
29
+ private actionLoop;
30
+ private acceptingEvents;
31
+ private closed;
32
+ private eventFailureSignalled;
33
+ private resolveEventFailure;
34
+ private readonly eventFailure;
35
+ constructor(options: ChannelBridgeOptions);
36
+ get busy(): boolean;
37
+ get whenStopped(): Promise<void>;
38
+ quiesce(): Promise<boolean>;
39
+ start(): Promise<void>;
40
+ close(): Promise<void>;
41
+ private handleChannelEvent;
42
+ private trackChannelEvent;
43
+ private drainEventHandlers;
44
+ private dispatchChannelEvent;
45
+ private consumeChatActions;
46
+ private handleAction;
47
+ private trackToolStart;
48
+ private trackToolReady;
49
+ private executeTool;
50
+ private dispatchToolCompletion;
51
+ }
52
+ export declare function parseToolInput(input: ChatToolCallReadyAction['toolInput']): Record<string, unknown>;