@lawaty/lacode 1.3.0-beta.2 → 1.3.0-beta.4

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/CHANGELOG.md CHANGED
@@ -7,6 +7,75 @@ All notable changes to LaCode are recorded here. The format follows
7
7
  Versions come from git release commits; the npm version is bumped in the same
8
8
  commit as the changelog entry it describes.
9
9
 
10
+ ## [1.3.0-beta.4] — session events count as life, and the timeline builds on idle
11
+
12
+ ### Fixed
13
+
14
+ - **The hang reaper now treats opencode session and task events as proof of
15
+ life.** A pooled claim was only kept off the silence clock by message traffic.
16
+ `session.status` (`busy`/`retry`), `session.updated`, `todo.updated`,
17
+ `session.compacted`, `permission.asked` and `question.asked` now refresh the
18
+ same activity clock, so a session opencode is actively working on is not
19
+ condemned as hung. The two existing bounds are unchanged: a genuinely silent
20
+ task is still reaped after the idle window, and `MAX_CLAIM_AGE_MS` still kills
21
+ a task that never goes quiet.
22
+ - **The timeline is built when a session goes idle, not only after the
23
+ five-minute debounce.** Pending work is flushed on `session.idle`, so a short
24
+ or one-shot run gets a timeline before the process can exit. The timer remains
25
+ the debounce for an active session.
26
+ - **The timeline keeps the session title.** Only the first tick fetched session
27
+ info; every later summarizer prompt said "Session: untitled". The title is now
28
+ carried on the per-session state.
29
+ - **A failed timeline summarization is logged with a level opencode accepts.**
30
+ The call passed the message as the log *level*, so the runtime rejected the
31
+ payload (`Expected "debug" | "info" | "error" | "warn"`) and the failure was
32
+ invisible. Failures now log at `warn` with a message, and the no-bullets line
33
+ at `debug`.
34
+
35
+ ## [1.3.0-beta.3] — the 1.3.0-beta.2 notes
36
+
37
+ ### Changed
38
+
39
+ - **Republished so the published tarball carries the 1.3.0-beta.2 changelog
40
+ entry.** No runtime change: the code is identical to 1.3.0-beta.2.
41
+
42
+ ## [1.3.0-beta.2] — a reason that survives, and a timeline that ships
43
+
44
+ ### Added
45
+
46
+ - **A durable failure journal.** Every failed or cancelled spawn appends one JSON
47
+ line to `failures.jsonl` in the pool state directory: timestamp, source, base,
48
+ slot, model, reason, elapsed time and the error text. `pool_status` prints the
49
+ last five under `recent failures:`, so a slot that dies without a cooldown is
50
+ no longer invisible afterwards.
51
+
52
+ ### Changed
53
+
54
+ - **A spawn the pool did not abort now records why it died.** An unclassified
55
+ `session.error`, or an assistant message error matching no known class, used to
56
+ release the claim and say nothing. It now records the slot, model and error
57
+ text, and the cancelled task card is rewritten with that reason. A stop the
58
+ user presses still produces no record and no rewrite, because it raises no
59
+ session error.
60
+
61
+ ### Fixed
62
+
63
+ - **The cancelled-task rewrite was unauthenticated.** It hand-rolled a `fetch`
64
+ PATCH and read `OPENCODE_SERVER_PASSWORD` from the plugin's own environment —
65
+ but that variable exists only in the server process, so the request always went
66
+ out unauthenticated and a password-protected server answered `401`. The reason
67
+ was then dropped and the caller saw a bare `Task cancelled`. The rewrite now
68
+ goes through the injected client's part update, which is already authenticated;
69
+ the raw request remains only as a fallback for a runtime without it.
70
+ - **The timeline sidebar was never bundled.** `src/tui/timeline.tsx` was added in
71
+ 1.2.0 but never imported by the TUI entry point, so `dist/tui.js` contained no
72
+ reference to it and no published version shipped the panel: it worked only on a
73
+ machine whose `tui.json` pointed at the raw source file. The TUI entry now
74
+ composes it, `solid-js` and `@opentui/solid` are treated as host-provided peers
75
+ instead of being bundled, and the automatic JSX transform is enabled so the
76
+ `@opentui/solid` pragma is honoured rather than silently compiling to
77
+ `React.createElement`.
78
+
10
79
  ## [1.3.0-beta.0] — provider readiness, timeline roots, and a reason on cancelled tasks
11
80
 
12
81
  ### Added
package/dist/index.js CHANGED
@@ -3017,9 +3017,13 @@ var agentPoolHooks = (async ({ client, serverUrl, directory }, options) => {
3017
3017
  event: async ({ event }) => {
3018
3018
  try {
3019
3019
  const props = event.properties ?? {};
3020
+ const noteLive = (sessionID) => {
3021
+ if (typeof sessionID === "string" && sessionID) noteActivity(s, sessionID);
3022
+ };
3020
3023
  if (event.type === "session.status") {
3021
3024
  const sessionID = typeof props.sessionID === "string" ? props.sessionID : void 0;
3022
3025
  const status2 = props.status;
3026
+ if (status2?.type === "busy" || status2?.type === "retry") noteLive(sessionID);
3023
3027
  const action = status2?.action;
3024
3028
  const provider = typeof action?.provider === "string" ? action.provider : void 0;
3025
3029
  const reason2 = action?.reason ?? "";
@@ -3193,7 +3197,10 @@ var agentPoolHooks = (async ({ client, serverUrl, directory }, options) => {
3193
3197
  }
3194
3198
  if (event.type === "session.created") {
3195
3199
  const info2 = props.info;
3196
- if (info2?.id && info2.parentID) bindChild(s, info2.parentID, info2.id);
3200
+ if (info2?.id && info2.parentID) {
3201
+ bindChild(s, info2.parentID, info2.id);
3202
+ noteLive(info2.id);
3203
+ }
3197
3204
  return;
3198
3205
  }
3199
3206
  if (event.type === "message.updated" || event.type === "message.part.updated") {
@@ -3235,6 +3242,18 @@ var agentPoolHooks = (async ({ client, serverUrl, directory }, options) => {
3235
3242
  }
3236
3243
  return;
3237
3244
  }
3245
+ if (event.type === "session.updated") {
3246
+ noteLive(props.info?.id);
3247
+ return;
3248
+ }
3249
+ if (event.type === "todo.updated" || event.type === "session.compacted") {
3250
+ noteLive(props.sessionID);
3251
+ return;
3252
+ }
3253
+ if (event.type === "permission.asked" || event.type === "question.asked") {
3254
+ noteLive(props.sessionID);
3255
+ return;
3256
+ }
3238
3257
  if (event.type !== "message.updated") return;
3239
3258
  const info = props.info;
3240
3259
  if (!info || info.role !== "assistant" || !info.error) return;
@@ -6770,6 +6789,18 @@ function timelineHooks(input, options) {
6770
6789
  if (state2.timer || state2.running) return;
6771
6790
  schedule(id, state2);
6772
6791
  };
6792
+ function flush(id) {
6793
+ if (disabled || disposed) return;
6794
+ const state2 = sessions.get(id);
6795
+ if (!state2) return;
6796
+ if (state2.root === false) return;
6797
+ if (!state2.pending || state2.running) return;
6798
+ if (state2.timer) {
6799
+ clearTimeout(state2.timer);
6800
+ state2.timer = void 0;
6801
+ }
6802
+ void tick(id, state2);
6803
+ }
6773
6804
  function schedule(id, state2) {
6774
6805
  if (state2.timer) clearTimeout(state2.timer);
6775
6806
  state2.timer = setTimeout(() => {
@@ -6866,7 +6897,7 @@ function timelineHooks(input, options) {
6866
6897
  return { ok: true, bullets: [] };
6867
6898
  } else {
6868
6899
  if (process.env.LACODE_TIMELINE_DEBUG === "1") {
6869
- log("no bullet lines", { sessionID: id, text: text.slice(0, 300) });
6900
+ log("debug", "timeline: no bullet lines", { sessionID: id, text: text.slice(0, 300) });
6870
6901
  }
6871
6902
  return { ok: false, bullets: [], error: "no bullet lines" };
6872
6903
  }
@@ -6894,6 +6925,7 @@ function timelineHooks(input, options) {
6894
6925
  return;
6895
6926
  }
6896
6927
  title = info.title;
6928
+ state2.title = info.title ?? state2.title;
6897
6929
  state2.root = !info.parentID && !isInternalTitle(info.title);
6898
6930
  if (!state2.root) {
6899
6931
  sessions.delete(id);
@@ -6906,14 +6938,14 @@ function timelineHooks(input, options) {
6906
6938
  if (!excerpt.text) {
6907
6939
  return;
6908
6940
  }
6909
- const result = await summarize(id, info.title ?? "untitled", excerpt.text, readEntries(id, 4));
6941
+ const result = await summarize(id, state2.title ?? info?.title ?? "untitled", excerpt.text, readEntries(id, 4));
6910
6942
  if (result.ok) {
6911
6943
  if (result.bullets.length) appendEntries(id, result.bullets, Date.now(), excerpt.next);
6912
6944
  state2.cursor = excerpt.next;
6913
6945
  state2.failures = 0;
6914
6946
  } else {
6915
6947
  state2.failures++;
6916
- log("summarize failed", { sessionID: id, error: result.error });
6948
+ log("warn", "timeline: summarize failed", { sessionID: id, error: result.error });
6917
6949
  }
6918
6950
  } catch (error) {
6919
6951
  debug("tick failed", { sessionID: id, error });
@@ -7021,6 +7053,11 @@ function timelineHooks(input, options) {
7021
7053
  event: async ({ event }) => {
7022
7054
  try {
7023
7055
  switch (event.type) {
7056
+ case "session.idle": {
7057
+ const id = event.properties.sessionID;
7058
+ if (id) flush(id);
7059
+ return;
7060
+ }
7024
7061
  case "message.updated": {
7025
7062
  const id = event.properties.info?.sessionID;
7026
7063
  if (!id) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawaty/lacode",
3
- "version": "1.3.0-beta.2",
3
+ "version": "1.3.0-beta.4",
4
4
  "description": "Ultimate opencode setup for Go users — cost-controlled pooled subagents, hang recovery, and notifications for opencode.",
5
5
  "license": "MIT",
6
6
  "type": "module",