@alwith-ai/dsh-agent 0.2.1 → 0.2.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/bridge.ts +63 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwith-ai/dsh-agent",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "ACP v2 agent built on DeepSeek Harness: fixed plugin compositions (standard / minimal / anchored / code / cordis), streaming turns with direct state reporting, permissions, resume with replay, multi-provider credentials and subscription login",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/bridge.ts CHANGED
@@ -22,6 +22,7 @@
22
22
  */
23
23
 
24
24
  import type { Context } from "@deepseek-ai/cordis"
25
+ import packageJson from "../package.json" with { type: "json" }
25
26
  import { randomUUID } from "node:crypto"
26
27
  import { isAbsolute } from "node:path"
27
28
  import { Readable, Writable } from "node:stream"
@@ -36,6 +37,8 @@ import {
36
37
  type CancelSessionNotification,
37
38
  type CloseSessionRequest,
38
39
  type CloseSessionResponse,
40
+ type ListSessionsRequest,
41
+ type ListSessionsResponse,
39
42
  type CompactionId,
40
43
  type InitializeResponse,
41
44
  type NewSessionRequest,
@@ -612,9 +615,15 @@ export function apply(ctx: Context, config: AcpConfig): void {
612
615
  .onRequest("initialize", (): InitializeResponse => {
613
616
  return {
614
617
  protocolVersion: ACP_PROTOCOL_VERSION,
615
- info: { name: "dsh-agent", title: "ALwith dsh bridge", version: "0.1.0" },
618
+ info: { name: "dsh-agent", title: "ALwith dsh bridge", version: packageJson.version },
616
619
  authMethods: [],
617
- capabilities: { session: { prompt: {} } },
620
+ capabilities: {
621
+ session: { prompt: {} },
622
+ // seedHistory: a host that keeps its own transcript can continue it here —
623
+ // `session/new` with `_meta.dsh.seedHistory: [{role, text}]` injects it as
624
+ // model-facing context (agent.inject) before the first turn.
625
+ _meta: { dsh: { seedHistory: true } },
626
+ },
618
627
  }
619
628
  })
620
629
  .onRequest("session/new", async (context): Promise<NewSessionResponse> => {
@@ -643,8 +652,34 @@ export function apply(ctx: Context, config: AcpConfig): void {
643
652
  })
644
653
  const record = sessions.get(sessionId)
645
654
  if (record === undefined) throw internalError("session record vanished during session/new")
655
+ const seed = seedHistoryOf(params._meta)
656
+ if (seed.length > 0) {
657
+ record.agent.inject(
658
+ createUserMessage({ content: [{ type: "text", text: seedTranscript(seed) }], source: { kind: "user" } }),
659
+ )
660
+ }
646
661
  return { sessionId, configOptions: await modelConfigOptions(record) }
647
662
  })
663
+ // v2 baseline: session/list is part of the `session: {}` surface, no capability key.
664
+ // Headers come from dsh's own persistence (the on-disk format stays private to it);
665
+ // `cwd` narrows to sessions started in that workspace, like the cli.
666
+ .onRequest("session/list", async (context): Promise<ListSessionsResponse> => {
667
+ assertOpen()
668
+ const params: ListSessionsRequest = context.params
669
+ const persistence = ctx.get("sessionPersistence")
670
+ if (persistence === undefined) throw internalError("session persistence is not mounted")
671
+ const headers = await persistence.list()
672
+ const sessions = headers
673
+ .filter(header => params.cwd === undefined || params.cwd === null || header.cwd === params.cwd)
674
+ .sort((a, b) => b.createdAt - a.createdAt)
675
+ .map(header => {
676
+ // Every bridge session is created with meta.cwd; a header without one is a
677
+ // persistence anomaly, not a session the host could resume.
678
+ if (header.cwd === undefined) throw internalError(`persisted session ${header.id} has no cwd`)
679
+ return { sessionId: header.id, cwd: header.cwd, updatedAt: new Date(header.createdAt).toISOString() }
680
+ })
681
+ return { sessions, nextCursor: null }
682
+ })
648
683
  .onRequest("session/resume", async (context): Promise<ResumeSessionResponse> => {
649
684
  assertOpen()
650
685
  const params: ResumeSessionRequest = context.params
@@ -901,3 +936,29 @@ function validateSessionParams(params: NewSessionRequest): void {
901
936
  throw invalidParams("mcpServers is not supported")
902
937
  }
903
938
  }
939
+
940
+ export interface SeedMessage {
941
+ role: "user" | "assistant"
942
+ text: string
943
+ }
944
+
945
+ /** `session/new` `_meta.dsh.seedHistory`: prior conversation the host kept, to inject as model-facing context. */
946
+ export function seedHistoryOf(meta: NewSessionRequest["_meta"]): SeedMessage[] {
947
+ const raw = (meta as { dsh?: { seedHistory?: unknown } } | null | undefined)?.dsh?.seedHistory
948
+ if (raw === undefined || raw === null) return []
949
+ if (!Array.isArray(raw)) throw invalidParams("seedHistory must be an array of {role, text}")
950
+ return raw.map((entry, index) => {
951
+ const role = (entry as { role?: unknown })?.role
952
+ const text = (entry as { text?: unknown })?.text
953
+ if ((role !== "user" && role !== "assistant") || typeof text !== "string") {
954
+ throw invalidParams(`seedHistory[${index}] must be {role: "user" | "assistant", text: string}`)
955
+ }
956
+ return { role, text }
957
+ })
958
+ }
959
+
960
+ /** dsh injects one user-authored context message; the transcript is framed so the model reads it as history. */
961
+ export function seedTranscript(seed: readonly SeedMessage[]): string {
962
+ const lines = seed.map(message => `${message.role === "user" ? "User" : "Assistant"}: ${message.text}`)
963
+ return `<prior_conversation>\n${lines.join("\n\n")}\n</prior_conversation>`
964
+ }