@leviyuan/lodestar 0.2.0 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leviyuan/lodestar",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,22 +1,13 @@
1
1
  /**
2
- * The system-prompt fragment Lodestar appends on every claude headless
3
- * launch. Carry-over of the original "channel instructions" from
4
- * Lodestar 1.x's MCP server, adapted for the streaming-card model where
5
- * Claude's own stdout already renders live in the Feishu group, so there
6
- * is no separate `reply` tool to call.
2
+ * Daemon model I/O contracts. Appended to claude's system prompt on
3
+ * every headless launch via `--append-system-prompt`. Three rules:
4
+ * inbound file marker, multi-content boundary marker, outbound file
5
+ * marker. Anything beyond pure I/O semantics (environment description,
6
+ * UX conventions, identity binding) was stripped 2026-05-16 — the
7
+ * model handles conversational flow natively, doesn't need to be told.
7
8
  */
8
9
  export const CHANNEL_INSTRUCTIONS = [
9
- 'You are running inside Lodestar, a daemon that bridges this session to a Feishu (Lark) group chat.',
10
- 'Your assistant text is streamed live as a Feishu card in that group; tool calls appear as collapsible panels; thinking is shown but de-emphasized. There is no separate reply tool your normal conversational output IS the reply.',
11
- '',
12
- 'Conventions for every turn:',
13
- '- Open with one short acknowledgement so the user sees you started.',
14
- '- Stream your conclusion before the turn ends; never end on a silent tool call. The card is your voice.',
15
- '- For long work, drop progress sentences between tool calls so the user is not staring at a loading dot.',
16
- '',
17
- 'Inbound user messages may carry a [file: /abs/path] hint when the user sent an image or attachment in Feishu. Read those files when relevant.',
18
- '',
19
- 'To send a local file or image back to the user in this Feishu group, write the marker `[[send: /abs/path]]` (absolute path) anywhere in your reply, preferably on its own line at the end. The daemon strips every marker from the displayed card and posts the file as a separate Feishu message. Emit the marker only when the user asked for a file or when delivering a generated artifact (screenshot, diagram, exported doc) — not for arbitrary paths.',
20
- '',
21
- 'The group name equals the working directory under $HOME and equals the Lodestar session name. Treat that binding as load-bearing — do not rename or move the directory.',
10
+ '- Text prefixed with `[file: /abs/path]` means a file is attached at that path; read it when relevant.',
11
+ '- A content block wrapped in `<u>...</u>` is an independent message treat each `<u>` element in a multi-content turn as a separate input, even when their texts concatenate visually (e.g. `<u>1</u><u>45</u>` is two messages, not the number `145`).',
12
+ '- Write `[[send: /abs/path]]` anywhere in your reply (preferably on its own line) to deliver that file as a separate message. The marker is stripped from the displayed text. Emit only when the user asked for a file or you are delivering a generated artifact.',
22
13
  ].join('\n')
package/src/session.ts CHANGED
@@ -511,14 +511,17 @@ export class Session {
511
511
  const wasBusy = this.currentTurn !== null || this.openingTurn
512
512
  this.pendingUserMessageCount++
513
513
  this.lastUserOpenId = userOpenId
514
- // When this msg will be merged with siblings into a multi-content
515
- // user turn (i.e. the SDK queued it because the daemon was busy),
516
- // prepend a `[#N]\n` ordinal so the model can tell the merged
517
- // blocks apart. Without it the harness renders multi-content text
518
- // back-to-back ("1"+"2"+"5"+"56"+"89" "1255689") and the model
519
- // can't see the original boundaries surfaced 2026-05-16 when a
520
- // 5-msg accumulator test got mis-summed as one big number.
521
- const wireText = wasBusy ? `[#${this.pendingUserMessageCount}]\n${text}` : text
514
+ // When the SDK will merge this msg with siblings into a multi-
515
+ // content user turn, wrap it in `<u>...</u>` so the model sees a
516
+ // structural boundary it actually attends to. Tried U+001E
517
+ // (ASCII Record Separator) first invisible and theoretically
518
+ // perfect, but Anthropic's tokenizer effectively drops control
519
+ // chars and `<u>1</u><u>45</u>` became "145" to the model
520
+ // (2026-05-16 accumulator test). HTML-tag wrap is visible but
521
+ // models parse `<tag>` boundaries very reliably from training.
522
+ // Solo (eager-open) msgs don't get wrapped — no sibling, no
523
+ // merge, no need. Contract declared in CHANNEL_INSTRUCTIONS.
524
+ const wireText = wasBusy ? `<u>${text}</u>` : text
522
525
  this.proc!.sendUserText(wireText, files)
523
526
  if (wasBusy && msgId) {
524
527
  // Hold the slot in the map even if the API call hasn't returned