@junghanacs/entwurf 0.12.1 → 0.12.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 (40) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +43 -0
  3. package/README.md +3 -5
  4. package/VERIFY.md +4 -4
  5. package/demo/README.md +5 -5
  6. package/demo/demo-baseline.sh +2 -2
  7. package/demo/demo.sh +3 -3
  8. package/docs/setup-clean-host.md +5 -5
  9. package/package.json +11 -10
  10. package/pi/entwurf-targets.json +3 -6
  11. package/pi/meta-bridge/.claude-plugin/marketplace.json +0 -1
  12. package/pi-extensions/lib/acp/acp-client.ts +2 -2
  13. package/pi-extensions/lib/acp/models.ts +7 -9
  14. package/run.sh +31 -21
  15. package/scripts/check-acp-backend-preflight.ts +1 -1
  16. package/scripts/check-acp-config.ts +3 -3
  17. package/scripts/check-acp-event-mapper.ts +1 -1
  18. package/scripts/check-acp-provider-surface.ts +1 -1
  19. package/scripts/check-acp-sdk-surface.ts +22 -22
  20. package/scripts/check-acp-session-reuse.ts +3 -3
  21. package/scripts/check-acp-session-store.ts +6 -6
  22. package/scripts/check-acp-tool-surface.ts +2 -2
  23. package/scripts/check-entwurf-session-identity.ts +9 -9
  24. package/scripts/check-meta-manifest-schema.py +145 -0
  25. package/scripts/check-model-lock.ts +2 -2
  26. package/scripts/check-package-source-routing.ts +4 -4
  27. package/scripts/meta-bridge-doctor.sh +1 -1
  28. package/scripts/meta-bridge-install.sh +17 -3
  29. package/scripts/meta-bridge-state.py +39 -10
  30. package/scripts/resolve-acp-bridge.ts +1 -1
  31. package/scripts/smoke-acp-bundled-mcp-live.ts +1 -1
  32. package/scripts/smoke-acp-carrier-augment-live.ts +1 -1
  33. package/scripts/smoke-acp-mcp-live.ts +1 -1
  34. package/scripts/smoke-acp-memory-containment-live.ts +8 -9
  35. package/scripts/smoke-acp-overlay-live.ts +8 -9
  36. package/scripts/smoke-acp-provider-live.ts +1 -1
  37. package/scripts/smoke-acp-raw-turn-live.ts +12 -11
  38. package/scripts/smoke-acp-session-reuse-live.ts +24 -9
  39. package/scripts/smoke-acp-skill-live.ts +1 -1
  40. package/scripts/smoke-meta-install-state.sh +3 -3
@@ -16,7 +16,7 @@ import type { ResolvedTarget } from "../pi-extensions/lib/entwurf-core.ts";
16
16
  import { getRegistryRouting } from "../pi-extensions/lib/entwurf-core.ts";
17
17
 
18
18
  const isRemote = process.argv[2] === "remote";
19
- const target: ResolvedTarget = { provider: "entwurf", model: "claude-sonnet-4-6", explicitOnly: false };
19
+ const target: ResolvedTarget = { provider: "entwurf", model: "claude-sonnet-5", explicitOnly: false };
20
20
 
21
21
  // Throws EntwurfRoutingError (non-zero exit) when the bridge cannot be resolved —
22
22
  // exactly the fail-fast the smoke wants to observe as a failure, not a pass.
@@ -46,7 +46,7 @@ import { generateSessionId } from "../pi-extensions/lib/entwurf-core.ts";
46
46
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
47
47
 
48
48
  const ACP_PROVIDER = "entwurf";
49
- const ACP_MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-4-6";
49
+ const ACP_MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
50
50
 
51
51
  const REAL_CONTROL_DIR = path.join(os.homedir(), ".pi", "entwurf-control");
52
52
  const SOCKET_SUFFIX = ".sock";
@@ -47,7 +47,7 @@ import { dirname, join, resolve } from "node:path";
47
47
  import { fileURLToPath } from "node:url";
48
48
 
49
49
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
50
- const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-4-6";
50
+ const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
51
51
  const PROVIDER = "entwurf";
52
52
  const TURN_TIMEOUT_MS = Number(process.env.ENTWURF_ACP_PROVIDER_TIMEOUT_MS) || 240_000;
53
53
 
@@ -29,7 +29,7 @@ import { fileURLToPath } from "node:url";
29
29
 
30
30
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
31
31
  const PROBE_SERVER = join(REPO_ROOT, "scripts", "fixtures", "probe-mcp-server.ts");
32
- const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-4-6";
32
+ const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
33
33
  const PROVIDER = "entwurf";
34
34
  const TURN_TIMEOUT_MS = Number(process.env.ENTWURF_ACP_PROVIDER_TIMEOUT_MS) || 240_000;
35
35
 
@@ -69,7 +69,7 @@ import {
69
69
  } from "../pi-extensions/lib/acp/tool-surface.ts";
70
70
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
71
71
 
72
- const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_MEMORY_MODEL ?? "claude-sonnet-4-6";
72
+ const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_MEMORY_MODEL ?? "claude-sonnet-5";
73
73
  const ALLOW_PATH_FALLBACK = process.env.ENTWURF_ACP_MEMORY_ALLOW_PATH_FALLBACK === "1";
74
74
  const RAW_TAIL_CAP = 64 * 1024;
75
75
 
@@ -78,15 +78,14 @@ function fail(msg: string): never {
78
78
  process.exit(1);
79
79
  }
80
80
 
81
- const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
82
-
83
81
  function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
84
- return Promise.race([
85
- p,
86
- sleep(ms).then((): never => {
87
- throw new Error(`${label} timed out after ${ms}ms`);
88
- }),
89
- ]);
82
+ let timer: ReturnType<typeof setTimeout> | undefined;
83
+ const timeout = new Promise<never>((_, reject) => {
84
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
85
+ });
86
+ return Promise.race([p, timeout]).finally(() => {
87
+ if (timer) clearTimeout(timer);
88
+ });
90
89
  }
91
90
 
92
91
  /** Recursively list files under `dir` whose path contains a `memory` segment. */
@@ -56,7 +56,7 @@ import {
56
56
  } from "../pi-extensions/lib/acp/tool-surface.ts";
57
57
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
58
58
 
59
- const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_OVERLAY_MODEL ?? "claude-sonnet-4-6";
59
+ const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_OVERLAY_MODEL ?? "claude-sonnet-5";
60
60
  const ALLOW_PATH_FALLBACK = process.env.ENTWURF_ACP_OVERLAY_ALLOW_PATH_FALLBACK === "1";
61
61
  const RAW_TAIL_CAP = 64 * 1024;
62
62
 
@@ -65,15 +65,14 @@ function fail(msg: string): never {
65
65
  process.exit(1);
66
66
  }
67
67
 
68
- const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
69
-
70
68
  function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
71
- return Promise.race([
72
- p,
73
- sleep(ms).then((): never => {
74
- throw new Error(`${label} timed out after ${ms}ms`);
75
- }),
76
- ]);
69
+ let timer: ReturnType<typeof setTimeout> | undefined;
70
+ const timeout = new Promise<never>((_, reject) => {
71
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
72
+ });
73
+ return Promise.race([p, timeout]).finally(() => {
74
+ if (timer) clearTimeout(timer);
75
+ });
77
76
  }
78
77
 
79
78
  if (process.env.LIVE !== "1") {
@@ -28,7 +28,7 @@ import { dirname, join, resolve } from "node:path";
28
28
  import { fileURLToPath } from "node:url";
29
29
 
30
30
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
31
- const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-4-6";
31
+ const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
32
32
  const PROVIDER = "entwurf";
33
33
  const TURN_TIMEOUT_MS = Number(process.env.ENTWURF_ACP_PROVIDER_TIMEOUT_MS) || 240_000;
34
34
 
@@ -3,7 +3,7 @@
3
3
  // LIVE=1 ./run.sh smoke-acp-raw-turn-live
4
4
  //
5
5
  // What this proves (and ONLY this): the pinned Claude ACP adapter
6
- // (@agentclientprotocol/claude-agent-acp@0.39.0) spawns, speaks the ACP wire
6
+ // (@agentclientprotocol/claude-agent-acp@0.54.1) spawns, speaks the ACP wire
7
7
  // protocol over stdio NDJSON, and returns one real model turn. It is the
8
8
  // bytes-flow proof that the S2a dep surface is not just installable but
9
9
  // actually drivable — before any provider/overlay/streamSimple code (S2b+).
@@ -35,7 +35,7 @@ import { ndJsonStream, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
35
35
  import { connectAcpClient } from "../pi-extensions/lib/acp/acp-client.ts";
36
36
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
37
37
 
38
- const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_RAW_TURN_MODEL ?? "claude-sonnet-4-6";
38
+ const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_RAW_TURN_MODEL ?? "claude-sonnet-5";
39
39
  const ALLOW_PATH_FALLBACK = process.env.ENTWURF_ACP_RAW_TURN_ALLOW_PATH_FALLBACK === "1";
40
40
  const RAW_TAIL_CAP = 64 * 1024; // cap captured raw NDJSON to 64KB tail on report.
41
41
 
@@ -44,17 +44,18 @@ function fail(msg: string): never {
44
44
  process.exit(1);
45
45
  }
46
46
 
47
- const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
48
-
49
47
  // Guard each RPC so a hung backend fails this smoke instead of the outer
50
- // process timeout — keeps the failure attributable to the right step.
48
+ // process timeout — keeps the failure attributable to the right step. Always
49
+ // clear the timeout; otherwise a successful live turn can print PASS and keep
50
+ // Node alive until the stale timer fires.
51
51
  function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
52
- return Promise.race([
53
- p,
54
- sleep(ms).then((): never => {
55
- throw new Error(`${label} timed out after ${ms}ms`);
56
- }),
57
- ]);
52
+ let timer: ReturnType<typeof setTimeout> | undefined;
53
+ const timeout = new Promise<never>((_, reject) => {
54
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
55
+ });
56
+ return Promise.race([p, timeout]).finally(() => {
57
+ if (timer) clearTimeout(timer);
58
+ });
58
59
  }
59
60
 
60
61
  if (process.env.LIVE !== "1") {
@@ -27,7 +27,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
27
27
  import type { Api, AssistantMessageEvent, Context, Model } from "@earendil-works/pi-ai";
28
28
 
29
29
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
30
- const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-4-6";
30
+ const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
31
31
  const TURN_TIMEOUT_MS = Number(process.env.ENTWURF_ACP_PROVIDER_TIMEOUT_MS) || 240_000;
32
32
 
33
33
  function fail(msg: string): never {
@@ -35,6 +35,16 @@ function fail(msg: string): never {
35
35
  process.exit(1);
36
36
  }
37
37
 
38
+ function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
39
+ let timer: ReturnType<typeof setTimeout> | undefined;
40
+ const timeout = new Promise<never>((_, reject) => {
41
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
42
+ });
43
+ return Promise.race([p, timeout]).finally(() => {
44
+ if (timer) clearTimeout(timer);
45
+ });
46
+ }
47
+
38
48
  if (process.env.LIVE !== "1") {
39
49
  console.error("[smoke-acp-session-reuse-live] skipped — set LIVE=1 to run the real 2-turn reuse acceptance.");
40
50
  process.exit(0);
@@ -100,13 +110,15 @@ async function main(): Promise<void> {
100
110
  },
101
111
  ],
102
112
  };
103
- const r1 = await Promise.race([
113
+ const r1 = await withTimeout(
114
+ "turn 1",
104
115
  consume(backend.streamShellAcp(model, turn1, options) as Stream),
105
- new Promise<never>((_, rej) => setTimeout(() => rej(new Error("turn 1 timed out")), TURN_TIMEOUT_MS)),
106
- ]);
116
+ TURN_TIMEOUT_MS,
117
+ );
107
118
  if (r1.error) fail(`turn 1 errored: ${r1.error}`);
108
119
  if (!r1.done) fail("turn 1 did not complete");
109
120
  console.error(`[smoke-acp-session-reuse-live] turn 1 reply: ${JSON.stringify(r1.text.slice(-120))}`);
121
+ console.error("[smoke-acp-session-reuse-live] starting turn 2 reuse prompt");
110
122
 
111
123
  // --- turn 2 (reuse): ask for the codeword, DELTA ONLY ---
112
124
  const turn2: Context = {
@@ -136,10 +148,11 @@ async function main(): Promise<void> {
136
148
  },
137
149
  ],
138
150
  };
139
- const r2 = await Promise.race([
151
+ const r2 = await withTimeout(
152
+ "turn 2",
140
153
  consume(backend.streamShellAcp(model, turn2, options) as Stream),
141
- new Promise<never>((_, rej) => setTimeout(() => rej(new Error("turn 2 timed out")), TURN_TIMEOUT_MS)),
142
- ]);
154
+ TURN_TIMEOUT_MS,
155
+ );
143
156
  if (r2.error) fail(`turn 2 errored: ${r2.error}`);
144
157
  if (!r2.done) fail("turn 2 did not complete");
145
158
  console.error(`[smoke-acp-session-reuse-live] turn 2 reply: ${JSON.stringify(r2.text.slice(-120))}`);
@@ -160,8 +173,10 @@ async function main(): Promise<void> {
160
173
 
161
174
  main()
162
175
  .then(() => {
163
- // retained child is SIGKILLed by the backend's process 'exit' hook.
164
- process.exit(0);
176
+ // Let stdout/stderr and finally cleanup drain naturally; the backend's process
177
+ // 'exit' hook still SIGKILLs the retained child when Node exits. A direct
178
+ // process.exit(0) can truncate the success/PASS log after the live turn.
179
+ process.exitCode = 0;
165
180
  })
166
181
  .catch((err) => {
167
182
  fail(err instanceof Error ? err.message : String(err));
@@ -30,7 +30,7 @@ import { dirname, join, resolve } from "node:path";
30
30
  import { fileURLToPath } from "node:url";
31
31
 
32
32
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
33
- const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-4-6";
33
+ const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
34
34
  const PROVIDER = "entwurf";
35
35
  const TURN_TIMEOUT_MS = Number(process.env.ENTWURF_ACP_PROVIDER_TIMEOUT_MS) || 240_000;
36
36
 
@@ -102,7 +102,7 @@ assert settings['cleanupPeriodDays'] == 365
102
102
  assert settings['env']['DISABLE_AUTOCOMPACT'] == '1'
103
103
  assert settings['env']['KEEP_ME'] == 'yes'
104
104
  assert settings['statusLine']['command'] == os.environ['REPO'] + '/scripts/meta-bridge-statusline.sh'
105
- for key in ['promptSuggestionEnabled','awaySummaryEnabled','autoMemoryEnabled','verbose','autoCompactEnabled','showTurnDuration','terminalProgressBarEnabled','useAutoModeDuringPlan']:
105
+ for key in ['promptSuggestionEnabled','awaySummaryEnabled','autoMemoryEnabled','verbose','autoCompactEnabled','showTurnDuration','terminalProgressBarEnabled','useAutoModeDuringPlan','enableWorkflows','workflowKeywordTriggerEnabled']:
106
106
  assert settings[key] is False, key
107
107
  assert settings['skipDangerousModePermissionPrompt'] is True
108
108
  for item in ['Bash','Read','Write','Edit','Grep','Glob','WebFetch','WebSearch','Skill','mcp__entwurf-bridge__*']:
@@ -192,7 +192,7 @@ assert settings['statusLine']['command'] == '/old/user/statusline.sh'
192
192
  assert settings['promptSuggestionEnabled'] is True
193
193
  assert settings['skipDangerousModePermissionPrompt'] is False
194
194
  assert settings['showTurnDuration'] is True
195
- for key in ['awaySummaryEnabled','autoMemoryEnabled','verbose','autoCompactEnabled','terminalProgressBarEnabled','useAutoModeDuringPlan']:
195
+ for key in ['awaySummaryEnabled','autoMemoryEnabled','verbose','autoCompactEnabled','terminalProgressBarEnabled','useAutoModeDuringPlan','enableWorkflows','workflowKeywordTriggerEnabled']:
196
196
  assert key not in settings, key
197
197
  allow=settings['permissions']['allow']
198
198
  deny=settings['permissions']['deny']
@@ -319,7 +319,7 @@ STORE="$TMP/store"; export STORE; mkdir -p "$STORE"
319
319
  valid_record "20260606T000000-aaaaaa" "native-a" > "$STORE/20260606T000000-aaaaaa.meta.json"
320
320
  valid_record "20260606T000001-bbbbbb" "native-b" > "$STORE/20260606T000001-bbbbbb.meta.json"
321
321
 
322
- STATUS_INPUT_MATCH='{"session_id":"native-a","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-sonnet-4-6"},"context_window":{"context_window_size":200000,"used_percentage":2,"current_usage":{"input_tokens":10}}}'
322
+ STATUS_INPUT_MATCH='{"session_id":"native-a","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-sonnet-5"},"context_window":{"context_window_size":200000,"used_percentage":2,"current_usage":{"input_tokens":10}}}'
323
323
  STATUS_INPUT_MISS='{"session_id":"native-missing","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-opus-4-8"}}'
324
324
  STATUS_INPUT_READY='{"workspace":{"current_dir":"/tmp"},"model":{"id":"claude-haiku-4-5"}}'
325
325
  STATUS_OUT_MATCH="$(printf '%s' "$STATUS_INPUT_MATCH" | ENTWURF_META_SESSIONS_DIR="$STORE" "$REPO/scripts/meta-bridge-statusline.sh")"