@alfe.ai/claude-code-host 0.1.1 → 0.1.3

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.
@@ -463,6 +463,7 @@ var SessionDriver = class {
463
463
  "--allowedTools",
464
464
  allowed
465
465
  ];
466
+ if (params.model) args.push("--model", params.model);
466
467
  if (params.claudeSessionId) args.push("--resume", params.claudeSessionId);
467
468
  if (params.mcpConfigPath) args.push("--mcp-config", params.mcpConfigPath);
468
469
  if (params.appendSystemPrompt) args.push("--append-system-prompt", params.appendSystemPrompt);
@@ -502,6 +503,9 @@ var SessionDriver = class {
502
503
  return;
503
504
  }
504
505
  this.child = child;
506
+ try {
507
+ child.stdin.end();
508
+ } catch {}
505
509
  const timeout = setTimeout(() => {
506
510
  this.log.warn("[session-driver] turn exceeded the time cap — aborting");
507
511
  try {
@@ -789,8 +793,20 @@ const VOICE_REPLY_SYSTEM_PROMPT = [
789
793
  ].join("\n");
790
794
  /** Cross-agent tool steering (every turn). Mirrors CROSS_AGENT_TOOLS_SYSTEM_PROMPT. */
791
795
  const CROSS_AGENT_TOOLS_SYSTEM_PROMPT = ["Cross-agent messaging: to contact another Alfe agent (IDs like agt_…), use list_agents to discover and message_agent to send.", "The runtime's built-in session tools do NOT know Alfe agent IDs and will fail — never use them to reach another agent."].join("\n");
792
- /** Persona files read from the workspace, in precedence order (first wins as lead). */
793
- const PERSONA_FILES = ["SOUL.md", "IDENTITY.md"];
796
+ /**
797
+ * Short, AUTHORITATIVE persona pointer for `--append-system-prompt`. The full
798
+ * persona (SOUL/IDENTITY/AGENTS/…) is delivered as the workspace `CLAUDE.md`,
799
+ * which Claude Code auto-loads every turn — so we do NOT re-dump it into the
800
+ * system prompt (doing both double-injected the persona, wasting tokens and
801
+ * risking drift). This system-level pointer just establishes the framing that
802
+ * CLAUDE.md-as-project-memory can't guarantee on its own: this is an Alfe chat
803
+ * agent, not a coding session.
804
+ */
805
+ const PERSONA_POINTER_SYSTEM_PROMPT = [
806
+ "You are an Alfe agent replying to a user over a chat bridge — NOT an interactive coding session.",
807
+ "Your identity, persona, voice, and operating instructions live in CLAUDE.md and the other files in your workspace.",
808
+ "Fully embody that persona in how you speak and act; respond AS that agent, not as a generic coding assistant."
809
+ ].join("\n");
794
810
  /**
795
811
  * One FIFO backlog PER claude-session key. Unlike openclaw-chat's single
796
812
  * per-daemon queue, the Claude Code host spawns a distinct `claude -p` per
@@ -979,7 +995,7 @@ var ClaudeCodeHost = class {
979
995
  } catch (err) {
980
996
  this.log.warn(`[claude-host] memoryLoadContext failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
981
997
  }
982
- const appendSystemPrompt = await this.buildSystemPrompt(memoryContext, isVoice);
998
+ const appendSystemPrompt = this.buildSystemPrompt(memoryContext, isVoice);
983
999
  const claudeSessionId = this.claudeSessionIds.get(bareSessionId) ?? null;
984
1000
  const driver = this.makeDriver();
985
1001
  this.runningTurns.set(sessionKey, req.id);
@@ -992,6 +1008,7 @@ var ClaudeCodeHost = class {
992
1008
  cwd: this.cfg.workspace,
993
1009
  ...this.cfg.mcpConfigPath ? { mcpConfigPath: this.cfg.mcpConfigPath } : {},
994
1010
  ...appendSystemPrompt ? { appendSystemPrompt } : {},
1011
+ ...this.cfg.model ? { model: this.cfg.model } : {},
995
1012
  onEmission: (emission) => {
996
1013
  this.forwardEmission(runId, sessionKey, emission);
997
1014
  }
@@ -1102,7 +1119,11 @@ var ClaudeCodeHost = class {
1102
1119
  }
1103
1120
  async handleSessionsList(req) {
1104
1121
  const params = req.params;
1105
- const sessions = await listSessions(params);
1122
+ const sessions = await listSessions({
1123
+ channel: params.channel,
1124
+ tenantId: params.tenantId,
1125
+ userId: params.userId
1126
+ });
1106
1127
  this.chat.sendResponse(req.id, true, { sessions });
1107
1128
  }
1108
1129
  async handleSessionsGet(req) {
@@ -1136,23 +1157,13 @@ var ClaudeCodeHost = class {
1136
1157
  activity: session.activity ?? []
1137
1158
  });
1138
1159
  }
1139
- async buildSystemPrompt(memoryContext, isVoice) {
1140
- const parts = [];
1141
- const persona = await this.readPersona();
1142
- if (persona) parts.push(persona);
1160
+ buildSystemPrompt(memoryContext, isVoice) {
1161
+ const parts = [PERSONA_POINTER_SYSTEM_PROMPT];
1143
1162
  if (memoryContext) parts.push(`# Relevant memory\n\n${memoryContext}`);
1144
1163
  if (isVoice) parts.push(VOICE_REPLY_SYSTEM_PROMPT);
1145
1164
  parts.push(CROSS_AGENT_TOOLS_SYSTEM_PROMPT);
1146
1165
  return parts.join("\n\n");
1147
1166
  }
1148
- async readPersona() {
1149
- const chunks = [];
1150
- for (const file of PERSONA_FILES) try {
1151
- const text = await (0, node_fs_promises.readFile)((0, node_path.join)(this.cfg.workspace, file), "utf-8");
1152
- if (text.trim()) chunks.push(text.trim());
1153
- } catch {}
1154
- return chunks.join("\n\n");
1155
- }
1156
1167
  async buildHistoryPreamble(bareSessionId) {
1157
1168
  const session = await getSession(bareSessionId);
1158
1169
  if (!session || session.messages.length === 0) return "";
@@ -1162,7 +1173,7 @@ var ClaudeCodeHost = class {
1162
1173
  }
1163
1174
  async ensureSession(bareSessionId, params) {
1164
1175
  if (await getSession(bareSessionId)) return;
1165
- await createSession(bareSessionId, params.agentId, params.clientType ?? "chat", params.tenantId, params.identityId ?? params.userId);
1176
+ await createSession(bareSessionId, params.agentId, params.clientType ?? "chat", params.tenantId, params.userId);
1166
1177
  }
1167
1178
  async loadClaudeSessionIds() {
1168
1179
  try {
@@ -1214,7 +1225,8 @@ function resolveHostConfig() {
1214
1225
  apiUrl: resolved.apiUrl,
1215
1226
  workspace: resolved.workspacePath,
1216
1227
  mcpConfigPath: (0, node_path.join)((0, node_os.homedir)(), ".claude-code", "mcp-config.json"),
1217
- claudeSessionsPath: (0, node_path.join)((0, node_os.homedir)(), ".alfe", "claude-code", "sessions.json")
1228
+ claudeSessionsPath: (0, node_path.join)((0, node_os.homedir)(), ".alfe", "claude-code", "sessions.json"),
1229
+ ...process.env.ALFE_DEFAULT_MODEL?.trim() ? { model: process.env.ALFE_DEFAULT_MODEL.trim() } : {}
1218
1230
  };
1219
1231
  }
1220
1232
  //#endregion
@@ -463,6 +463,7 @@ var SessionDriver = class {
463
463
  "--allowedTools",
464
464
  allowed
465
465
  ];
466
+ if (params.model) args.push("--model", params.model);
466
467
  if (params.claudeSessionId) args.push("--resume", params.claudeSessionId);
467
468
  if (params.mcpConfigPath) args.push("--mcp-config", params.mcpConfigPath);
468
469
  if (params.appendSystemPrompt) args.push("--append-system-prompt", params.appendSystemPrompt);
@@ -502,6 +503,9 @@ var SessionDriver = class {
502
503
  return;
503
504
  }
504
505
  this.child = child;
506
+ try {
507
+ child.stdin.end();
508
+ } catch {}
505
509
  const timeout = setTimeout(() => {
506
510
  this.log.warn("[session-driver] turn exceeded the time cap — aborting");
507
511
  try {
@@ -789,8 +793,20 @@ const VOICE_REPLY_SYSTEM_PROMPT = [
789
793
  ].join("\n");
790
794
  /** Cross-agent tool steering (every turn). Mirrors CROSS_AGENT_TOOLS_SYSTEM_PROMPT. */
791
795
  const CROSS_AGENT_TOOLS_SYSTEM_PROMPT = ["Cross-agent messaging: to contact another Alfe agent (IDs like agt_…), use list_agents to discover and message_agent to send.", "The runtime's built-in session tools do NOT know Alfe agent IDs and will fail — never use them to reach another agent."].join("\n");
792
- /** Persona files read from the workspace, in precedence order (first wins as lead). */
793
- const PERSONA_FILES = ["SOUL.md", "IDENTITY.md"];
796
+ /**
797
+ * Short, AUTHORITATIVE persona pointer for `--append-system-prompt`. The full
798
+ * persona (SOUL/IDENTITY/AGENTS/…) is delivered as the workspace `CLAUDE.md`,
799
+ * which Claude Code auto-loads every turn — so we do NOT re-dump it into the
800
+ * system prompt (doing both double-injected the persona, wasting tokens and
801
+ * risking drift). This system-level pointer just establishes the framing that
802
+ * CLAUDE.md-as-project-memory can't guarantee on its own: this is an Alfe chat
803
+ * agent, not a coding session.
804
+ */
805
+ const PERSONA_POINTER_SYSTEM_PROMPT = [
806
+ "You are an Alfe agent replying to a user over a chat bridge — NOT an interactive coding session.",
807
+ "Your identity, persona, voice, and operating instructions live in CLAUDE.md and the other files in your workspace.",
808
+ "Fully embody that persona in how you speak and act; respond AS that agent, not as a generic coding assistant."
809
+ ].join("\n");
794
810
  /**
795
811
  * One FIFO backlog PER claude-session key. Unlike openclaw-chat's single
796
812
  * per-daemon queue, the Claude Code host spawns a distinct `claude -p` per
@@ -979,7 +995,7 @@ var ClaudeCodeHost = class {
979
995
  } catch (err) {
980
996
  this.log.warn(`[claude-host] memoryLoadContext failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
981
997
  }
982
- const appendSystemPrompt = await this.buildSystemPrompt(memoryContext, isVoice);
998
+ const appendSystemPrompt = this.buildSystemPrompt(memoryContext, isVoice);
983
999
  const claudeSessionId = this.claudeSessionIds.get(bareSessionId) ?? null;
984
1000
  const driver = this.makeDriver();
985
1001
  this.runningTurns.set(sessionKey, req.id);
@@ -992,6 +1008,7 @@ var ClaudeCodeHost = class {
992
1008
  cwd: this.cfg.workspace,
993
1009
  ...this.cfg.mcpConfigPath ? { mcpConfigPath: this.cfg.mcpConfigPath } : {},
994
1010
  ...appendSystemPrompt ? { appendSystemPrompt } : {},
1011
+ ...this.cfg.model ? { model: this.cfg.model } : {},
995
1012
  onEmission: (emission) => {
996
1013
  this.forwardEmission(runId, sessionKey, emission);
997
1014
  }
@@ -1102,7 +1119,11 @@ var ClaudeCodeHost = class {
1102
1119
  }
1103
1120
  async handleSessionsList(req) {
1104
1121
  const params = req.params;
1105
- const sessions = await listSessions(params);
1122
+ const sessions = await listSessions({
1123
+ channel: params.channel,
1124
+ tenantId: params.tenantId,
1125
+ userId: params.userId
1126
+ });
1106
1127
  this.chat.sendResponse(req.id, true, { sessions });
1107
1128
  }
1108
1129
  async handleSessionsGet(req) {
@@ -1136,23 +1157,13 @@ var ClaudeCodeHost = class {
1136
1157
  activity: session.activity ?? []
1137
1158
  });
1138
1159
  }
1139
- async buildSystemPrompt(memoryContext, isVoice) {
1140
- const parts = [];
1141
- const persona = await this.readPersona();
1142
- if (persona) parts.push(persona);
1160
+ buildSystemPrompt(memoryContext, isVoice) {
1161
+ const parts = [PERSONA_POINTER_SYSTEM_PROMPT];
1143
1162
  if (memoryContext) parts.push(`# Relevant memory\n\n${memoryContext}`);
1144
1163
  if (isVoice) parts.push(VOICE_REPLY_SYSTEM_PROMPT);
1145
1164
  parts.push(CROSS_AGENT_TOOLS_SYSTEM_PROMPT);
1146
1165
  return parts.join("\n\n");
1147
1166
  }
1148
- async readPersona() {
1149
- const chunks = [];
1150
- for (const file of PERSONA_FILES) try {
1151
- const text = await readFile(join(this.cfg.workspace, file), "utf-8");
1152
- if (text.trim()) chunks.push(text.trim());
1153
- } catch {}
1154
- return chunks.join("\n\n");
1155
- }
1156
1167
  async buildHistoryPreamble(bareSessionId) {
1157
1168
  const session = await getSession(bareSessionId);
1158
1169
  if (!session || session.messages.length === 0) return "";
@@ -1162,7 +1173,7 @@ var ClaudeCodeHost = class {
1162
1173
  }
1163
1174
  async ensureSession(bareSessionId, params) {
1164
1175
  if (await getSession(bareSessionId)) return;
1165
- await createSession(bareSessionId, params.agentId, params.clientType ?? "chat", params.tenantId, params.identityId ?? params.userId);
1176
+ await createSession(bareSessionId, params.agentId, params.clientType ?? "chat", params.tenantId, params.userId);
1166
1177
  }
1167
1178
  async loadClaudeSessionIds() {
1168
1179
  try {
@@ -1214,7 +1225,8 @@ function resolveHostConfig() {
1214
1225
  apiUrl: resolved.apiUrl,
1215
1226
  workspace: resolved.workspacePath,
1216
1227
  mcpConfigPath: join(homedir(), ".claude-code", "mcp-config.json"),
1217
- claudeSessionsPath: join(homedir(), ".alfe", "claude-code", "sessions.json")
1228
+ claudeSessionsPath: join(homedir(), ".alfe", "claude-code", "sessions.json"),
1229
+ ...process.env.ALFE_DEFAULT_MODEL?.trim() ? { model: process.env.ALFE_DEFAULT_MODEL.trim() } : {}
1218
1230
  };
1219
1231
  }
1220
1232
  //#endregion
package/dist/index.d.cts CHANGED
@@ -521,6 +521,41 @@ declare class AgentApiClient {
521
521
  scopes: string;
522
522
  }[];
523
523
  }>;
524
+ /**
525
+ * Pattern A: provider-parameterized multi-account credential fetch for the
526
+ * social connectors (Bluesky, and the approval-gated backlog: X, Meta,
527
+ * Threads, LinkedIn, Pinterest, TikTok, Reddit, YouTube).
528
+ *
529
+ * Unlike the bespoke `getGithubAccounts()` / `getXeroAccounts()` shapes,
530
+ * this returns a UNIFORM normalized account shape so `@alfe.ai/social-mcp`'s
531
+ * shared driver can require a single `account` selector on every
532
+ * credential-touching tool regardless of platform. The backend
533
+ * `api-agents/{provider}/accounts` route is already provider-generic; this
534
+ * is the client-side normalization the plan (`do-we-need-any-moonlit-toucan`
535
+ * Phase 0, step 5) calls for.
536
+ *
537
+ * `accountIdentifier` is the stable per-account selector the LLM should
538
+ * pass back (for Bluesky: the account DID). `accessToken` carries whatever
539
+ * the provider's `buildCredentialsResponse` bundles (for Bluesky: the JSON
540
+ * session bundle — the driver parses the `accessJwt` out of it, or reads the
541
+ * top-level `accessJwt` from `providerMetadata`-adjacent fields). Everything
542
+ * else the driver needs for routing (handle, pdsHost, did, …) is on
543
+ * `providerMetadata`.
544
+ *
545
+ * Token refresh is delegated to connect (never done in-plugin) via
546
+ * `POST /agent/connect/{provider}/refresh` — not exposed here.
547
+ */
548
+ getSocialAccounts(provider: string): Promise<{
549
+ provider: string;
550
+ accounts: {
551
+ connectionId: string;
552
+ accountIdentifier: string;
553
+ displayName: string | null;
554
+ accessToken: string;
555
+ providerMetadata: Record<string, unknown>;
556
+ connectedAt: string;
557
+ }[];
558
+ }>;
524
559
  /**
525
560
  * @deprecated Returns a single primary credential blob (legacy "pick-the-
526
561
  * default-connection" shape). Use `getXeroAccounts()` for the multi-
@@ -1770,6 +1805,8 @@ interface RunTurnParams {
1770
1805
  mcpConfigPath?: string;
1771
1806
  /** Persona + reply-style text appended to the system prompt. Omitted → none. */
1772
1807
  appendSystemPrompt?: string;
1808
+ /** Bare Claude model id for `--model` (subscription models only). Omitted → CLI default. */
1809
+ model?: string;
1773
1810
  /** Tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS). */
1774
1811
  allowedTools?: readonly string[];
1775
1812
  /** Permission mode (defaults to DEFAULT_PERMISSION_MODE — never bypass). */
@@ -1838,6 +1875,13 @@ interface ClaudeCodeHostConfig {
1838
1875
  mcpConfigPath?: string;
1839
1876
  /** Path to persist Alfe sessionId → claude session id. */
1840
1877
  claudeSessionsPath?: string;
1878
+ /**
1879
+ * Bare Claude model id for `claude -p --model` (e.g. `claude-opus-4-8` or the
1880
+ * `opus`/`sonnet`/`haiku` aliases). Sourced from the agent's `defaultModel`
1881
+ * via `ALFE_DEFAULT_MODEL`. Omitted → the operator's `claude` CLI default.
1882
+ * NEVER provider-qualified (`anthropic/…`) — that's an OpenClaw-only shape.
1883
+ */
1884
+ model?: string;
1841
1885
  logger?: Logger;
1842
1886
  }
1843
1887
  declare class ClaudeCodeHost {
@@ -1889,7 +1933,6 @@ declare class ClaudeCodeHost {
1889
1933
  private handleSessionsList;
1890
1934
  private handleSessionsGet;
1891
1935
  private buildSystemPrompt;
1892
- private readPersona;
1893
1936
  private buildHistoryPreamble;
1894
1937
  private ensureSession;
1895
1938
  private loadClaudeSessionIds;
package/dist/index.d.ts CHANGED
@@ -521,6 +521,41 @@ declare class AgentApiClient {
521
521
  scopes: string;
522
522
  }[];
523
523
  }>;
524
+ /**
525
+ * Pattern A: provider-parameterized multi-account credential fetch for the
526
+ * social connectors (Bluesky, and the approval-gated backlog: X, Meta,
527
+ * Threads, LinkedIn, Pinterest, TikTok, Reddit, YouTube).
528
+ *
529
+ * Unlike the bespoke `getGithubAccounts()` / `getXeroAccounts()` shapes,
530
+ * this returns a UNIFORM normalized account shape so `@alfe.ai/social-mcp`'s
531
+ * shared driver can require a single `account` selector on every
532
+ * credential-touching tool regardless of platform. The backend
533
+ * `api-agents/{provider}/accounts` route is already provider-generic; this
534
+ * is the client-side normalization the plan (`do-we-need-any-moonlit-toucan`
535
+ * Phase 0, step 5) calls for.
536
+ *
537
+ * `accountIdentifier` is the stable per-account selector the LLM should
538
+ * pass back (for Bluesky: the account DID). `accessToken` carries whatever
539
+ * the provider's `buildCredentialsResponse` bundles (for Bluesky: the JSON
540
+ * session bundle — the driver parses the `accessJwt` out of it, or reads the
541
+ * top-level `accessJwt` from `providerMetadata`-adjacent fields). Everything
542
+ * else the driver needs for routing (handle, pdsHost, did, …) is on
543
+ * `providerMetadata`.
544
+ *
545
+ * Token refresh is delegated to connect (never done in-plugin) via
546
+ * `POST /agent/connect/{provider}/refresh` — not exposed here.
547
+ */
548
+ getSocialAccounts(provider: string): Promise<{
549
+ provider: string;
550
+ accounts: {
551
+ connectionId: string;
552
+ accountIdentifier: string;
553
+ displayName: string | null;
554
+ accessToken: string;
555
+ providerMetadata: Record<string, unknown>;
556
+ connectedAt: string;
557
+ }[];
558
+ }>;
524
559
  /**
525
560
  * @deprecated Returns a single primary credential blob (legacy "pick-the-
526
561
  * default-connection" shape). Use `getXeroAccounts()` for the multi-
@@ -1770,6 +1805,8 @@ interface RunTurnParams {
1770
1805
  mcpConfigPath?: string;
1771
1806
  /** Persona + reply-style text appended to the system prompt. Omitted → none. */
1772
1807
  appendSystemPrompt?: string;
1808
+ /** Bare Claude model id for `--model` (subscription models only). Omitted → CLI default. */
1809
+ model?: string;
1773
1810
  /** Tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS). */
1774
1811
  allowedTools?: readonly string[];
1775
1812
  /** Permission mode (defaults to DEFAULT_PERMISSION_MODE — never bypass). */
@@ -1838,6 +1875,13 @@ interface ClaudeCodeHostConfig {
1838
1875
  mcpConfigPath?: string;
1839
1876
  /** Path to persist Alfe sessionId → claude session id. */
1840
1877
  claudeSessionsPath?: string;
1878
+ /**
1879
+ * Bare Claude model id for `claude -p --model` (e.g. `claude-opus-4-8` or the
1880
+ * `opus`/`sonnet`/`haiku` aliases). Sourced from the agent's `defaultModel`
1881
+ * via `ALFE_DEFAULT_MODEL`. Omitted → the operator's `claude` CLI default.
1882
+ * NEVER provider-qualified (`anthropic/…`) — that's an OpenClaw-only shape.
1883
+ */
1884
+ model?: string;
1841
1885
  logger?: Logger;
1842
1886
  }
1843
1887
  declare class ClaudeCodeHost {
@@ -1889,7 +1933,6 @@ declare class ClaudeCodeHost {
1889
1933
  private handleSessionsList;
1890
1934
  private handleSessionsGet;
1891
1935
  private buildSystemPrompt;
1892
- private readPersona;
1893
1936
  private buildHistoryPreamble;
1894
1937
  private ensureSession;
1895
1938
  private loadClaudeSessionIds;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/claude-code-host",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Chat bridge that makes a self-hosted Claude Code agent reachable from Alfe chat on the user's Claude subscription",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "dist"
20
20
  ],
21
21
  "dependencies": {
22
- "@alfe.ai/agent-api-client": "^0.11.0",
22
+ "@alfe.ai/agent-api-client": "^0.11.1",
23
23
  "@alfe.ai/chat": "^0.0.16",
24
24
  "@alfe.ai/config": "^0.3.0"
25
25
  },