@bitefoundry/retrieviewer 0.0.14 → 0.0.16

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.
@@ -102,6 +102,19 @@ records the exact36 source-key indices and `pixelBlending:false`. No new generat
102
102
  or photographic input, asset family or attribution is added. Existing CC BY-SA4.0
103
103
  terms and all non-watch pixels remain unchanged. See WATCH-REDRAW.md for the A/B.
104
104
 
105
+ Local attention-paw candidate2026-10-24 appends six built-in imagegen whole-body
106
+ keys to the existing comfort archive. Inputs were the existing licensed seated
107
+ puppy and a generated paw study; no new photographs were introduced. All45 old
108
+ comfort frames remain exact. ATTENTION-PAW.md records the source-sheet hash,
109
+ prompt specification, registration and byte costs. Existing attribution and
110
+ CC BY-SA4.0 terms apply; source sheets are excluded from npm.
111
+
112
+ Local standing-wall candidate2026-10-24 appends nine generated whole-body keys,
113
+ referenced only from the existing canonical puppy. All51 accepted comfort frames
114
+ remain exact. Source hash, prompt specification, common-scale registration and
115
+ costs are in ATTENTION-WALL.md. No new photo/video inputs or raw npm sheets;
116
+ existing attribution and CC BY-SA4.0 terms continue to apply.
117
+
105
118
  Copyrightability of AI-generated expression, the precise derivative-work analysis,
106
119
  and worldwide name clearance are not conclusively determined here. Existing
107
120
  domestic trademark research records possible similar marks; owning an npm scope
@@ -0,0 +1,53 @@
1
+ # Attention paw gesture
2
+
3
+ The notification sequence now approaches the user when necessary, sits facing
4
+ them, makes two gentle air scrapes with the viewer-left forepaw, lowers it, and
5
+ looks toward the pending item. This is one 1.88-second episode per accepted
6
+ notice. It does not add autonomous reminders or change which events qualify.
7
+
8
+ The viewer-right forepaw remains planted. Six whole-body grayscale keys share
9
+ one uniform scale and are translated to that support point. There are no
10
+ independently warped heads, legs, or body parts. The original seated frame is
11
+ reused exactly at both ends. Resolution, ball, treat, and tug interrupt through
12
+ an authored lowering path of at most 0.3 seconds from the last displayed key.
13
+
14
+ ## Art and provenance
15
+
16
+ Generated with the built-in imagegen tool, using the existing canonical seated
17
+ puppy and an earlier generated paw study as references. No new photographs were
18
+ used. Existing art attribution and CC BY-SA 4.0 terms continue to apply; see
19
+ ASSET-PROVENANCE.md and ASSETS-LICENSE.md.
20
+
21
+ Final source: `dist/attention-paw-v2/source.png`.
22
+ SHA-256: `0c3b01952378cd7c0fd941f53e1c43ca5de73c63b3875e58d035b009508d693a`.
23
+
24
+ Prompt specification: a wide 3-by-2 sheet of six square, black-background cells;
25
+ the same fluffy golden-retriever puppy, face, bandana, seated proportions and
26
+ complete tail in every cell; one forepaw gently lifts, bends, reaches forward,
27
+ scrapes down through the air, retracts, and lowers; the opposite forepaw stays
28
+ planted; consistent fixed camera, floor, body scale, monochrome tonal fur and
29
+ clean empty margins. Earlier alternating-paw and tail-clipped studies were
30
+ rejected and are not runtime assets.
31
+
32
+ `tools/prepare-attention-art.mjs` and its Python companion extract and register
33
+ the six keys offline. They append to the existing comfort archive and verify
34
+ all 45 previous frames byte-for-byte. The source sheet and QA files are excluded
35
+ from the npm package. No generation service is used at runtime.
36
+
37
+ ## Cost and verification
38
+
39
+ The compressed archive grows by 55,386 bytes (about 54 KiB). Node's compressed
40
+ pixel storage grows by 63,912 bytes (about 62 KiB), compared with the same codec
41
+ over the preserved 45-frame baseline. Selectively retaining the new keys in the
42
+ portable store costs about 137 KiB; this is different from the net archive
43
+ repacking delta. These are pixel-storage measurements, not total process RSS.
44
+
45
+ The sampler borrows the existing RGBA scratch and two-frame reconstruction
46
+ cache. No new runtime dependency, asset family, timer, or permanent decoded
47
+ animation cache is added. Dedicated tests check exact old pixels, planted root,
48
+ neutral bookends, bounded recovery, direct interactions and the public renderer.
49
+
50
+ The owner accepted this motion on2026-10-24 for release0.0.16, alongside the
51
+ standing wall variant. Windows npm-shim support is a separate compatibility fix
52
+ in that release. Completed-work alerts remain unsupported. The local preview
53
+ uses simulated pending items. Live companion restart remains user-controlled.
@@ -0,0 +1,58 @@
1
+ # Standing wall scrabble
2
+
3
+ An additional attention variant: shift weight to the hindquarters, rise onto two
4
+ hind feet, brace both front paws against an implicit front glass plane, scrabble
5
+ quickly, then lower and sit. The complete authored episode lasts 2.2 seconds.
6
+ The glass is a visual metaphor, not detected window geometry or a new collision
7
+ system. No actual desktop content, wall or screen is inspected.
8
+
9
+ The gentle seated paw cue is preserved. Accepted notices alternate paw/wall
10
+ without consuming autonomy or fetch randomness. Hosts can request a fixed cue:
11
+
12
+ ```js
13
+ pet.notify({kind:'input', gesture:'wall'}); // 'auto' (default), 'paw', 'wall'
14
+ ```
15
+
16
+ Repeated or rejected notices do not start another episode. If wall art is absent,
17
+ automatic mode keeps the prior behavior; an explicit wall request returns false.
18
+ Resolution, ball, treat and tug lower from the exact displayed key via an authored
19
+ path of at most 0.4 seconds. There is no mid-gesture teleport to a sitting frame.
20
+
21
+ ## Art source
22
+
23
+ Built-in imagegen, canonical existing licensed seated puppy as the sole image
24
+ reference. No new photographs or video frames were used. The artwork retains
25
+ the existing attribution and CC BY-SA 4.0 lineage. Raw sheets are excluded from
26
+ npm. Source: `dist/attention-wall-v1/source.png`, SHA-256:
27
+ `ea1a8f2f4d3f4b897bec95bcd06b8a4d6cd81d083713b67857efac469707e4af`.
28
+
29
+ Prompt specification: the exact reference puppy, muzzle/ears/bandana/fur and age;
30
+ one 3-by-3 sheet of complete whole-body keys on black, a fixed front/slight
31
+ three-quarter camera, same world scale and floor in every cell, no drawn wall,
32
+ props or labels. Keys: seated weight shift, half-rise, upright braced on two rear
33
+ soles with both forepaws high, alternating forepaw scrapes with coupled shoulder
34
+ weight transfer, both paws high, knees bending/lowering, and low seated recovery.
35
+ Preserve chest/skull volume, full tail and toes; standing must be naturally taller
36
+ than sitting rather than resized into the seated bounding box.
37
+
38
+ Actual sheet: 1254 by 1254, split into nine 418-square cells. Offline preparation
39
+ uses one common scale (0.7572463768115942), whole-frame translation to the measured
40
+ lower paw-pair proxy, and no anatomical compositing or nonlinear warp. Proxy
41
+ registration is not animal motion capture. The exact old neutral is reused at
42
+ both ends. Existing 51 comfort frames, including the gentle paw cue, stay exact.
43
+
44
+ ## Resource cost and checks
45
+
46
+ Nine extra keys extend the same comfort archive from 51 to 60 frames. Compressed
47
+ download increases 104,865 bytes (102.4 KiB); Node compressed pixel storage grows
48
+ 117,578 bytes (114.8 KiB) with the same codec/baseline. These are payload figures,
49
+ not total RSS. No new RGBA scratch, timer, asset family, process or dependency.
50
+
51
+ Tests cover old pixel hashes, shared neutral, malformed recovery metadata,
52
+ unchanged world/projection, sole-proxy registration, every recovery phase,
53
+ variant choice and resolution/direct-play priority. Real public Braille frames
54
+ and the Aside preview were inspected. Raster audit has no structural errors;
55
+ its center-shift warnings describe the deliberate rise/lower and stance transfer
56
+ and require visual review rather than a looser threshold. The owner visually
57
+ accepted this motion and authorized release0.0.16 on2026-10-24. The user's running
58
+ companion is not automatically replaced by publication.
package/ATTENTION.md CHANGED
@@ -42,6 +42,10 @@ Only the fixed read-only command `stackbite agent seats --output json`, at most
42
42
  once every five seconds after the previous read finishes. StackBite revalidates
43
43
  the inherited caller Seat/generation. The source requires a live Room binding;
44
44
  launching from an unrelated terminal is refused rather than guessing credentials.
45
+
46
+ On Windows, a native executable or npm PowerShell launcher is resolved on PATH.
47
+ The launcher uses literal `-File` argv with closed stdin, without a shell command
48
+ string or execution-policy change. Unix retains the direct command.
45
49
  No raw database, credential file, terminal transcript, screenshot, microphone,
46
50
  clipboard, provider hook or browser session is read by Retrieviewer.
47
51
 
@@ -58,8 +62,21 @@ a remote server does not automatically give a local Windows pet remote access.
58
62
 
59
63
  ## Behavior and limits
60
64
 
61
- - A new exact Room/Seat/generation/turn/request cues one connected sit-and-look-left.
65
+ Attention now has two authored gestures: a seated gentle paw and a standing
66
+ two-hind-foot glass scrabble. Automatic cues alternate; hosts can pass
67
+ `gesture:'paw'` or `gesture:'wall'` to `pet.notify`. See [wall motion](ATTENTION-WALL.md)
68
+ for support, recovery and measured costs. The preview's wall button forces that
69
+ variant using simulated input; it does not request a real approval.
70
+
71
+ - A new exact Room/Seat/generation/turn/request cues one connected attention episode:
72
+ brake/recover and face the user, approach using the existing world path if more
73
+ than 0.8 m from the near check-in point, then sit, gently paw the air twice,
74
+ lower the paw and look left. See [paw motion and cost](ATTENTION-PAW.md). Near pets stay
75
+ in place. Resolution during approach brakes the obsolete journey naturally.
62
76
  Left means the sidebar direction, not a detected button coordinate.
77
+ - The rule cue is accepted before optional inference starts. LLM latency, failure
78
+ or disagreement never delays, repeats or retracts a confirmed pending alert.
79
+ This does not remove the existing five-second source polling interval.
63
80
  - Explicit ball/treat/tug interactions win. A rejected cue retries only while
64
81
  its source fact is fresh. Distinct cues are at least 20 seconds apart.
65
82
  - Resolved/disconnected/stale facts clear the cue. Source errors are unavailable,
@@ -76,11 +93,35 @@ These are separate short runs, not a steady-state/peak comparison or a guarantee
76
93
  of 1 MiB overhead. Native-helper and transient StackBite CLI memory are additional.
77
94
  No new image payload, model or runtime package dependency was added.
78
95
 
79
- Windows additionally displays a small count/offline label in the existing control
80
- bar. Non-Windows helpers do not yet display that label; their live StackBite
96
+ Windows displays the pending Seat's bounded `role_title` and approval/input kind
97
+ in the existing control bar, with ellipsis and a full-text hover tooltip. The
98
+ local `watch.target` and `attentionLabel` diagnostics expose that same target.
99
+ Names are at most 64 UTF-16 code units, control/directional characters are removed,
100
+ and no task text is retained. Labels never enter model input or saved settings.
101
+ Renaming/replacing a target refreshes the label even if the count is unchanged;
102
+ renaming alone is not a new alert. Missing names use the Seat ID as a fallback.
103
+ Non-Windows helpers do not yet display that label; their live StackBite
81
104
  integration remains unverified. Capture/overlay support is a separate platform
82
105
  question. No new Wayland or AlmaLinux compatibility claim is made here.
83
106
 
107
+ Version 0.0.15 includes these rule-first/target/approach improvements after the
108
+ 0.0.14 baseline. A simulated frozen
109
+ LLM and a time-skipped distant notification are available in the local preview.
110
+ They never write StackBite state or make model requests. Real provider events
111
+ still depend on StackBite publishing trustworthy `needs_input` metadata.
112
+
113
+ Working-copy verification on 2026-10-24: focused tests cover immediate delivery
114
+ with hung/disagreeing advice, local-only bounded labels, same-count renaming,
115
+ late-result cancellation, far-to-near motion, and resolution during approach.
116
+ An owned Windows worker/native-helper run displayed and renamed the actual label,
117
+ cleared it to `Watching`, preserved direct ball priority and exited both children.
118
+ The fixture run used about 112 MiB Node RSS (no LLM), excluding the helper/CLI.
119
+ Matched short watch-off native startup probes of published 0.0.14 and the working
120
+ copy both rounded to 106 MiB Node RSS. These are not steady-state or peak-memory
121
+ guarantees. All nine packaged art archives are byte-identical; no dependency,
122
+ additional resident process or observer timer was added. Labels are bounded
123
+ metadata, not a promise of literally zero additional bytes or allocations.
124
+
84
125
  ## Reuse decision
85
126
 
86
127
  The first source already exists in StackBite, so a fixed-argv adapter is smaller
package/BRAIN.md CHANGED
@@ -55,17 +55,21 @@ truncated output, tool calls and responses over 32 KiB. No returned text is run,
55
55
  typed, used as a coordinate, or displayed as a factual summary.
56
56
 
57
57
  This first connection is **advisory on known pending-input facts**, not screen
58
- understanding. A confirmed approval/question cannot be suppressed by an LLM:
59
- `notify` follows the model; `ignore` is overridden by the rule; uncertain/errors
60
- fall back. Diagnostics distinguish `model_notify`, `rule_override`, and
61
- `rule_fallback`. With only kind/count facts, the model adds little semantic value;
58
+ understanding. Since 0.0.15, a confirmed approval/question is
59
+ delivered by the rule first; optional model work starts only after the pet accepts
60
+ the cue. It cannot delay, suppress, repeat or retract the cue. Diagnostics show
61
+ `rule_first` while advice is pending, then `model_notify` for agreement,
62
+ `rule_override` for disagreement, or `rule_fallback` for uncertain/errors.
63
+ The 0.0.14 baseline waited for advice. With only kind/count facts, the model adds little semantic value;
62
64
  this slice establishes a bounded backend before optional richer perception.
63
65
 
64
- Each exact pending item is judged once while fresh. Resolution, generation
66
+ Each accepted exact pending item is judged once while fresh; rejected cues during
67
+ direct play do not spend requests. Seat display names stay local. Resolution, generation
65
68
  replacement, stale source, pause, hide and shutdown cancel its request; late
66
69
  answers cannot resurrect a notice. Direct ball/treat/tug still wins. Animation
67
- ticks never await HTTP. Network timeout is 3 seconds; a host-tick guard falls back
68
- within 3.5 seconds of active time even if a custom judge hangs. `brain-check`
70
+ ticks never await HTTP. Network timeout is 3 seconds; a host-tick guard discards
71
+ hung advice within 3.5 seconds of active time, without delaying the already-issued
72
+ rule alert. `brain-check`
69
73
  allows 8 seconds for diagnosis, not for normal pet reactions.
70
74
 
71
75
  ## Resource and request limits
@@ -109,7 +113,13 @@ pending event, and worker/painter both exited. Node RSS was about 128 MiB in tha
109
113
  short run, excluding helper/CLI memory. This is not an incremental or steady-state
110
114
  RAM measurement. Existing live companion state was not changed.
111
115
 
112
- All 217 applicable tests passed (two macOS-only checks skipped), including mocked
116
+ For the 0.0.14 baseline, all 217 applicable tests passed (two macOS-only checks skipped), including mocked
113
117
  JSON validation, price ceilings, timeout, cache, budget, cancellation and stale
114
118
  results. The earlier free Gemma attempt returned 429 and never produced a verified
115
119
  live decision; it is no longer the selected backend. Privacy restrictions remain.
120
+
121
+ The 0.0.15 rule-first candidate passed 223 applicable tests (two macOS-only checks
122
+ skipped), fresh installed-consumer checks and an owned Windows fixture run with
123
+ actual native target-label renaming/clearing. Hung/disagreeing advice is tested
124
+ with mock judges: those tests do not make paid requests. The model request body
125
+ and privacy/price restrictions are unchanged.
package/README.ko.md CHANGED
@@ -108,7 +108,13 @@ npm uninstall -g @bitefoundry/retrieviewer
108
108
 
109
109
  ## StackBite 알림
110
110
 
111
+ 0.0.16은 앞발로 살살 부르기와 두 발로 서서 벽 박박 긁기를 번갈아 보여줘요.
112
+ 홈페이지에서도 두 동작을 눌러볼 수 있어요. Windows의 npm 설치형 StackBite도
113
+ 감시할 수 있게 했어요. 작업 완료 체크표시는 아직 알림 대상이 아니에요.
114
+
111
115
  0.0.14부터 [승인·입력 대기 알림](ATTENTION.md)을 선택해서 켤 수 있어요.
116
+ 0.0.15는 LLM을 기다리지 않고 알리고, Windows에 Seat 이름과 대기 종류를 표시해요.
117
+ 강아지도 기존 그림으로 가까이 와서 알림 방향을 봐요. 이름은 외부로 보내지 않아요.
112
118
  화면 캡처나 자동 승인 없이 현재 Room의 메타데이터만 읽어요.
113
119
  업데이트 후, 화면이 보이는 PC의 실행 중인 StackBite Seat에서 실행하세요.
114
120
 
package/README.md CHANGED
@@ -137,6 +137,15 @@ It is opt-in and does not capture screens or operate other apps.
137
137
  An [optional paid DeepSeek/OpenRouter backend](./BRAIN.md) can judge anonymous event metadata;
138
138
  offline rules remain the default and preserve confirmed pending alerts.
139
139
 
140
+ Version 0.0.15 alerts without waiting for the model, shows the Seat name and
141
+ pending kind locally on Windows, and lets the puppy approach before looking
142
+ toward the sidebar. It reuses existing artwork; names never enter model input.
143
+
144
+ Version 0.0.16 adds two approved attention gestures: a gentle seated paw and a
145
+ standing two-paw glass scrabble. They alternate for new alerts; both are playable
146
+ in the website demo. Windows watching also supports npm's PowerShell launcher.
147
+ Completed-work checkmarks are not alert events yet.
148
+
140
149
  After updating, run inside a live StackBite Seat on the display computer:
141
150
 
142
151
  ```sh
package/RELEASING.md CHANGED
@@ -1,4 +1,39 @@
1
- # Retrieviewer 0.0.14 release process
1
+ # Retrieviewer 0.0.16 release process
2
+
3
+ Current target: `@bitefoundry/retrieviewer@0.0.16`, public `latest`.
4
+ On2026-10-24 the owner accepted the gentle-paw and standing-wall gestures and
5
+ explicitly authorized merging, GitHub push, npm publication and website updates.
6
+ Both cues preserve the accepted puppy; automatic notifications alternate them.
7
+ Explicit host gesture selection and bilingual website demo buttons are included.
8
+ The same comfort archive gains15 keys; all45 pre-gesture keys remain exact.
9
+ Windows read-only watching resolves native executables or npm PowerShell shims
10
+ with literal argv and closed stdin. No shell string or execution-policy change.
11
+
12
+ Build/install one fresh exact tarball, run full tests and owned Windows checks,
13
+ push and wait for Windows/Linux CI, publish the same artifact, verify registry
14
+ integrity and a fresh consumer, and verify matching deployed Pages runtime bytes.
15
+ No StackBite patch, completed-work alerts, screen capture, saved API key, live-pet
16
+ restart or additional platform certification is part of this release.
17
+
18
+ ## Previous 0.0.15 scope (historical)
19
+
20
+ Current target: `@bitefoundry/retrieviewer@0.0.15`, public `latest`.
21
+ On 2026-10-24 the owner authorized npm publication of the reviewed rule-first
22
+ attention update. The optional model now runs after a confirmed rule cue is
23
+ accepted; no model response can delay, retract or repeat that cue. Windows
24
+ displays a bounded local Seat name and approval/input kind with a hover tooltip.
25
+ The pet reuses existing front contact, travel and whole-body look material;
26
+ resolved approaches brake naturally and direct play keeps priority.
27
+
28
+ Keep all nine art archives, runtime dependencies, provider/privacy restrictions,
29
+ price ceilings and request bounds unchanged. Build/install a fresh exact tarball,
30
+ compare every shipped byte, run the full suite and owned native checks, then
31
+ publish that same artifact. Verify registry integrity and a fresh public-registry
32
+ consumer. No StackBite changes, real approval actions, screen capture, host patch,
33
+ live pet restart or website deployment are authorized by this release request.
34
+ macOS/Linux live notification behavior remains unverified.
35
+
36
+ ## Previous 0.0.14 scope (historical)
2
37
 
3
38
  Current target: `@bitefoundry/retrieviewer@0.0.14`, public `latest`.
4
39
  On 2026-10-24 the owner authorized npm publication of the metadata-only StackBite
@@ -2,10 +2,26 @@
2
2
  // Uses StackBite's own binding validation; never reads its credential files.
3
3
  import {execFile} from 'node:child_process';
4
4
  import {promisify} from 'node:util';
5
+ import {stat} from 'node:fs/promises';
6
+ import {join,delimiter} from 'node:path';
5
7
  import {validateAttentionSnapshot} from '../../retrieviewer-attention.mjs';
6
- const execute=promisify(execFile);
8
+ const run=promisify(execFile);
9
+ const execute=(command,args,options)=>{const pending=run(command,args,options);pending.child?.stdin?.end();return pending;};
7
10
  const identifier=value=>typeof value==='string'&&/^[a-zA-Z0-9_.:-]{1,128}$/.test(value);
8
11
 
12
+ // Match npm's Windows PowerShell launcher without shell strings or execution-
13
+ // policy changes. Like the installer probe, close stdin so npm sees no pipeline.
14
+ export async function resolveStackBiteCommand({env=process.env,platform=process.platform}={}){
15
+ if(platform==='win32')for(const directory of (env.PATH??env.Path??'').split(delimiter).filter(Boolean)){
16
+ for(const extension of ['.exe','.ps1']){
17
+ const path=join(directory,'stackbite'+extension);
18
+ if(!(await stat(path).catch(()=>null))?.isFile())continue;
19
+ return extension==='.exe'?{command:path,prefix:[]}:{command:'powershell.exe',prefix:['-NoProfile','-NonInteractive','-File',path]};
20
+ }
21
+ }
22
+ return {command:'stackbite',prefix:[]};
23
+ }
24
+
9
25
  export function stackBiteBinding(env=process.env){
10
26
  if(!identifier(env.STACKBITE_ROOM_ID)||!identifier(env.STACKBITE_SEAT_KEY)||!identifier(env.STACKBITE_SESSION_GENERATION)||!env.STACKBITE_STATE_DIR||!env.STACKBITE_ROOM_BRIDGE_URL)throw Error('Start desktop --watch stackbite inside a live StackBite Seat. No Room binding was found.');
11
27
  return {roomId:env.STACKBITE_ROOM_ID};
@@ -24,23 +40,24 @@ export function projectStackBiteSeats(value,{roomId}={}){
24
40
  const turnId=Number.isSafeInteger(status.turn_id)&&status.turn_id>0?status.turn_id:null;
25
41
  const requestId=identifier(status.input_request_id)?status.input_request_id:undefined;
26
42
  if(turnId===null&&requestId===undefined)continue;
27
- items.push({id:seat.seat_key,generation:seat.generation,turnId,...requestId===undefined?{}:{requestId},kind:status.input_kind==='approval'?'approval':'input'});
43
+ items.push({id:seat.seat_key,generation:seat.generation,turnId,...requestId===undefined?{}:{requestId},kind:status.input_kind==='approval'?'approval':'input',label:seat.role_title});
28
44
  }else{
29
45
  // Older explicit lifecycle facts are accepted only with an exact turn.
30
46
  if(!['awaiting_user','blocked'].includes(seat.lifecycle_state)||!Number.isSafeInteger(seat.turn_id)||seat.turn_id<1)continue;
31
- items.push({id:seat.seat_key,generation:seat.generation,turnId:seat.turn_id,kind:seat.provider_permission_pending===true||seat.input_kind==='approval'?'approval':'input'});
47
+ items.push({id:seat.seat_key,generation:seat.generation,turnId:seat.turn_id,kind:seat.provider_permission_pending===true||seat.input_kind==='approval'?'approval':'input',label:seat.role_title});
32
48
  }
33
49
  }
34
50
  return validateAttentionSnapshot({source:'stackbite',scope:roomId,items});
35
51
  }
36
- export function createStackBiteSource({env=process.env,exec=execute}={}){
52
+ export function createStackBiteSource({env=process.env,exec=execute,resolveCommand=()=>resolveStackBiteCommand({env})}={}){
37
53
  const binding=stackBiteBinding(env);
38
54
  const childEnv={...env};delete childEnv.OPENROUTER_API_KEY;
39
55
  return {
40
56
  async readSnapshot({signal}={}){
41
57
  // Fixed argv, no shell, no input/click/report/approval command. The CLI checks
42
58
  // that the inherited caller Seat/generation is still live on every read.
43
- const {stdout}=await exec('stackbite',['agent','seats','--output','json'],{env:childEnv,signal,encoding:'utf8',windowsHide:true,timeout:4000,maxBuffer:262144});
59
+ const {command,prefix}=await resolveCommand();
60
+ const {stdout}=await exec(command,[...prefix,'agent','seats','--output','json'],{env:childEnv,signal,encoding:'utf8',windowsHide:true,timeout:4000,maxBuffer:262144});
44
61
  return projectStackBiteSeats(JSON.parse(stdout),binding);
45
62
  },
46
63
  };
@@ -110,12 +110,12 @@ sealed class Companion : ApplicationContext {
110
110
  readonly JavaScriptSerializer json=new JavaScriptSerializer();readonly object inputLock=new object();
111
111
  readonly Queue<Dictionary<string,object>> messages=new Queue<Dictionary<string,object>>();
112
112
  TargetItem target;Form pickerWindow;QuietWindow testTarget;readonly bool testMode;Rectangle canvas;int revision=0,cell=8;bool visible=false,paused=false,loaded=false,quitting=false,picking=false,announced=false;
113
- Label status=new Label();Mutex mutex;string attentionText=null;
113
+ Label status=new Label();readonly ToolTip attentionTip=new ToolTip();Mutex mutex;string attentionText=null;
114
114
  internal Companion(Mutex single,bool diagnostic){
115
115
  mutex=single;testMode=diagnostic;bar.Text="Retrieviewer controls";bar.Size=new Size(538,40);bar.BackColor=Color.FromArgb(28,30,34);bar.ForeColor=Color.White;
116
116
  bar.Font=new Font("Segoe UI",9);bar.Controls.Add(MakeButton("Ball",8,()=>Interact("ball")));bar.Controls.Add(MakeButton("Treat",76,()=>Interact("treat")));
117
117
  bar.Controls.Add(MakeButton("Tug",144,()=>Interact("tug")));bar.Controls.Add(MakeButton("Window",212,ChooseTarget));bar.Controls.Add(MakeButton("Pause",280,TogglePause));bar.Controls.Add(MakeButton("Quit",348,Quit));
118
- status.AutoSize=false;status.Location=new Point(422,12);status.Size=new Size(112,22);status.Text="Loading...";bar.Controls.Add(status);
118
+ status.AutoSize=false;status.AutoEllipsis=true;status.UseMnemonic=false;status.Location=new Point(422,12);status.Size=new Size(112,22);status.Text="Loading...";bar.Controls.Add(status);
119
119
  ContextMenuStrip menu=new ContextMenuStrip();menu.Items.Add("Choose window",null,(s,e)=>ChooseTarget());menu.Items.Add("Hide / resume",null,(s,e)=>TogglePause());menu.Items.Add("Quit",null,(s,e)=>Quit());
120
120
  tray.Icon=SystemIcons.Application;tray.Text="Retrieviewer · Choose window";tray.ContextMenuStrip=menu;tray.Visible=true;tray.DoubleClick+=(s,e)=>ChooseTarget();
121
121
  // Handles are created on the UI thread before the reader can signal EOF.
@@ -154,8 +154,8 @@ sealed class Companion : ApplicationContext {
154
154
  if(type=="interaction"){status.Text=attentionText??(Program.Bool(m,"accepted")?"Ready!":"Please wait");continue;}
155
155
  if(type=="attention"){
156
156
  int count=Program.Int(m,"count");string state=Program.Str(m,"state");
157
- if(count<0||count>32)throw new Exception("Invalid attention count");
158
- attentionText=state=="connected"?(count>0?count+" need input":"Watching"):state=="paused"?"Watch paused":state=="connecting"?"Connecting...":"Watch offline";status.Text=attentionText;
157
+ attentionText=Program.AttentionText(state,count,m.ContainsKey("label")?Program.Str(m,"label"):"",m.ContainsKey("kind")?Program.Str(m,"kind"):"");status.Text=attentionText;attentionTip.SetToolTip(status,attentionText);
158
+ Program.Emit(new {type="attention-label",text=attentionText});
159
159
  continue;
160
160
  }
161
161
  if(type=="error"){status.Text="Engine error";Quit();return;}
@@ -216,7 +216,7 @@ sealed class Companion : ApplicationContext {
216
216
  }
217
217
  }
218
218
  void Quit(){if(quitting)return;quitting=true;clock.Stop();if(pickerWindow!=null)pickerWindow.Close();pet.Close();bar.Close();tray.Visible=false;tray.Dispose();Program.Emit(new {type="quit"});ExitThread();}
219
- protected override void Dispose(bool disposing){if(disposing){clock.Dispose();pet.Dispose();bar.Dispose();tray.Dispose();if(testTarget!=null)testTarget.Dispose();if(mutex!=null){mutex.ReleaseMutex();mutex.Dispose();mutex=null;}}base.Dispose(disposing);}
219
+ protected override void Dispose(bool disposing){if(disposing){clock.Dispose();attentionTip.Dispose();pet.Dispose();bar.Dispose();tray.Dispose();if(testTarget!=null)testTarget.Dispose();if(mutex!=null){mutex.ReleaseMutex();mutex.Dispose();mutex=null;}}base.Dispose(disposing);}
220
220
  }
221
221
 
222
222
  static class Program {
@@ -226,6 +226,17 @@ static class Program {
226
226
  internal static int Int(Dictionary<string,object> m,string key){return Convert.ToInt32(m[key]);}
227
227
  internal static string Str(Dictionary<string,object> m,string key){return Convert.ToString(m[key]);}
228
228
  internal static bool Bool(Dictionary<string,object> m,string key){return Convert.ToBoolean(m[key]);}
229
+ internal static string AttentionText(string state,int count,string label,string kind){
230
+ if(count<0||count>32||label.Length>64)throw new Exception("Invalid attention metadata");
231
+ foreach(char c in label)if(char.IsControl(c)||(c>=0x200b&&c<=0x200f)||(c>=0x202a&&c<=0x202e)||(c>=0x2060&&c<=0x206f))throw new Exception("Invalid attention label");
232
+ if(state=="connected"){
233
+ if(count==0)return "Watching";
234
+ if(label.Length==0)return count+" need input";
235
+ if(kind!="approval"&&kind!="input")throw new Exception("Invalid attention kind");
236
+ return label+" - "+(kind=="approval"?"Approval pending":"Input pending")+(count>1?" (+"+(count-1)+")":"");
237
+ }
238
+ return state=="paused"?"Watch paused":state=="connecting"?"Connecting...":"Watch offline";
239
+ }
229
240
  [STAThread] static int Main(string[] args){
230
241
  try{
231
242
  // GUI-subsystem processes have redirected handles but no console code
@@ -269,8 +280,9 @@ static class Program {
269
280
  Native.ShowWindow(target.Handle,4);Application.DoEvents();bool restored=!Native.IsIconic(target.Handle);
270
281
  bool focus=Native.GetForegroundWindow()==foreground;
271
282
  bool gridBounds=GridCell(3840,2160)==8&&GridCell(7680,4320)==12&&GridCell(32000,18000)==0&&GridCell(int.MaxValue,int.MaxValue)==0&&GridCell(0,0)==0;
272
- bool passed=clickThrough&&noActivate&&layered&&follows&&minimized&&hidden&&restored&&focus&&pet.PaintedDots>0&&gridBounds;
273
- Emit(new {type="self-test",passed=passed,gridBounds=gridBounds,clickThroughStyle=clickThrough,noActivateStyle=noActivate,layered=layered,followGeometry=follows,expectedX=expectedX,expectedY=expectedY,actualX=actual.Left,actualY=actual.Top,minimize=minimized,hide=hidden,restore=restored,foregroundPreserved=focus,dots=pet.PaintedDots,proof=proof});return passed?0:1;
283
+ bool labels=AttentionText("connected",2,"Worker & Review","approval")=="Worker & Review - Approval pending (+1)"&&AttentionText("connected",0,"","")=="Watching";
284
+ bool passed=clickThrough&&noActivate&&layered&&follows&&minimized&&hidden&&restored&&focus&&pet.PaintedDots>0&&gridBounds&&labels;
285
+ Emit(new {type="self-test",passed=passed,attentionLabels=labels,gridBounds=gridBounds,clickThroughStyle=clickThrough,noActivateStyle=noActivate,layered=layered,followGeometry=follows,expectedX=expectedX,expectedY=expectedY,actualX=actual.Left,actualY=actual.Top,minimize=minimized,hide=hidden,restore=restored,foregroundPreserved=focus,dots=pet.PaintedDots,proof=proof});return passed?0:1;
274
286
  }
275
287
  }
276
288
  }
@@ -10,7 +10,7 @@ import {controlAddress,parseHelperSpec} from '../desktop/platform.mjs';
10
10
 
11
11
  let helper,pump,timer,server,session,paths,owned=false,stopping=false,frames=0,paintedFrames=0,visible=false,selected=false,pickerVisible=false,pendingViewport=null,lastInteraction=null;
12
12
  let nativeError=null,ready=false,detached=false;
13
- let attention=null,brain=null;
13
+ let attention=null,brain=null,attentionLabel=null;
14
14
  const returnStyle=process.env.RETRIEVIEWER_RETURN_STYLE??'auto';
15
15
  const watchSource=process.env.RETRIEVIEWER_WATCH??'off';
16
16
  const brainSource=process.env.RETRIEVIEWER_BRAIN??'off';
@@ -44,7 +44,7 @@ try{
44
44
  body+=bytes.toString('utf8');if(body.length>2048)return socket.destroy();if(!body.includes('\n'))return;
45
45
  let command;try{command=JSON.parse(body.split('\n')[0]);}catch{return socket.destroy();}
46
46
  if(command.token!==session.token||!['status','stop'].includes(command.command))return socket.destroy();
47
- socket.end(JSON.stringify(command.command==='stop'?{stopped:true}:{running:true,reachable:true,selected,visible,pickerVisible,frames,paintedFrames,lastInteraction,returnStyle,watch:{source:watchSource,...attention?.status??{state:'off',pendingCount:0}},brain:brain?.status??{provider:'off',state:'off'},pid:process.pid,painterPid:helper?.pid,rssBytes:process.memoryUsage().rss})+'\n');
47
+ socket.end(JSON.stringify(command.command==='stop'?{stopped:true}:{running:true,reachable:true,selected,visible,pickerVisible,frames,paintedFrames,lastInteraction,returnStyle,attentionLabel,watch:{source:watchSource,...attention?.status??{state:'off',pendingCount:0}},brain:brain?.status??{provider:'off',state:'off'},pid:process.pid,painterPid:helper?.pid,rssBytes:process.memoryUsage().rss})+'\n');
48
48
  if(command.command==='stop')stop();
49
49
  });
50
50
  });
@@ -66,6 +66,7 @@ try{
66
66
  else if(m.type==='picker')pickerVisible=m.visible===true;
67
67
  else if(m.type==='viewport'){pendingViewport=m;visible=m.visible;selected=m.selected===true;pump?.viewport(m);}
68
68
  else if(m.type==='ack'){frames++;if(m.painted)paintedFrames++;pump?.ack();}
69
+ else if(m.type==='attention-label'){if(typeof m.text!=='string'||m.text.length>160)throw Error('Invalid attention label');attentionLabel=m.text;}
69
70
  else if(m.type==='pause')pump?.pause(m.paused);
70
71
  else if(m.type==='interact'){const accepted=pump?.interact(m.kind)??false;lastInteraction={kind:m.kind,accepted};write({type:'interaction',kind:m.kind,accepted});}
71
72
  else if(m.type==='quit')stop();
@@ -83,7 +84,7 @@ try{
83
84
  attention=createAttentionMonitor({readSnapshot:source.readSnapshot,...brain?{judgeNotice:input=>brain.decide(input)}:{},onNotice:notice=>pump.notify(notice),onClear:()=>pump.clearNotice(),onChange:status=>{
84
85
  // Older POSIX helpers deliberately reject unknown message types. They can
85
86
  // run the visual cue, but only the Windows bar implements this status label.
86
- if(spec.mode==='windows-overlay')write({type:'attention',state:status.state,count:status.pendingCount});
87
+ if(spec.mode==='windows-overlay')write({type:'attention',state:status.state,count:status.pendingCount,label:status.target?.label??'',kind:status.target?.kind??''});
87
88
  }});
88
89
  }
89
90
  write({type:'loaded',ink:process.env.RETRIEVIEWER_INK??'cream'});timer=setInterval(()=>{try{attention?.tick({active:!pump.hidden});pump.tick();}catch(error){write({type:'error',message:error.message});stop();}},1000/spec.fps);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitefoundry/retrieviewer",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "A fluffy Braille desktop pet for Windows and Linux X11, with experimental macOS support and an embeddable TUI engine.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://bitefoundry.github.io/retrieviewer/",
@@ -90,6 +90,8 @@
90
90
  "integrations/openrouter/brain.d.mts",
91
91
  "BRAIN.md",
92
92
  "ATTENTION.md",
93
+ "ATTENTION-PAW.md",
94
+ "ATTENTION-WALL.md",
93
95
  "integrations/linux-overlay/overlay.py",
94
96
  "integrations/linux-overlay/README.md",
95
97
  "integrations/macos-overlay/overlay.jxa",
@@ -6,7 +6,8 @@ export function comfortMotionKey(progress,motion){
6
6
  while(key<motion.times.length-1&&age>=motion.times[key+1])key++;
7
7
  return key;
8
8
  }
9
- export function comfortMotion(pose,info){return pose?.clip==='scramble'?info.scramble:pose?.clip==='pickup'?info.pickups?.[pose.view]:pose?.clip==='carry-trot'?info.carryTrot:pose?.clip==='carry-settle'?info.carryTrot?.settle:null;}
9
+ export function comfortMotion(pose,info){const attention=pose?.clip==='attention-paw'?info.attentionPaw:pose?.clip==='attention-wall'?info.attentionWall:null;return attention?(pose.release?attention.releases?.[pose.release.from]:attention):pose?.clip==='scramble'?info.scramble:pose?.clip==='pickup'?info.pickups?.[pose.view]:pose?.clip==='carry-trot'?info.carryTrot:pose?.clip==='carry-settle'?info.carryTrot?.settle:null;}
10
+ export const comfortProgress=pose=>pose.release?.progress??pose.progress;
10
11
  function validateMotion(motion,count){
11
12
  if(!motion||!Array.isArray(motion.frames)||!Array.isArray(motion.times)||motion.frames.length<2||motion.frames.length>32||motion.frames.length!==motion.times.length||motion.times[0]!==0||!Number.isFinite(motion.duration)||motion.duration<=0||motion.duration>3||(motion.cyclic?motion.times.at(-1)>=motion.duration:motion.times.at(-1)!==motion.duration)||motion.frames.some(i=>!Number.isInteger(i)||i<0||i>=count)||motion.times.some((t,i)=>!Number.isFinite(t)||(i&&t<=motion.times[i-1])))throw Error('Invalid fetch motion metadata');
12
13
  if(!Array.isArray(motion.floorOffsets)||motion.floorOffsets.length!==motion.frames.length||motion.floorOffsets.some(v=>!Number.isFinite(v)||Math.abs(v)>64))throw Error('Invalid fetch floor metadata');
@@ -33,16 +34,21 @@ export function createComfortSpriteSampler(bytes,info,output){
33
34
  }
34
35
  if(info.version>=4){validateMotion(info.scramble,info.frames);for(const view of ['rear','front'])validateMotion(info.pickups?.[view],info.frames);}
35
36
  if(info.version>=6){validateMotion(info.carryTrot,info.frames);validateMotion(info.carryTrot?.settle,info.frames);if(info.carryTrot.cyclic!==true||!info.carryTrot.mouth||!info.carryTrot.settle.mouth||!Array.isArray(info.carryTrot.feet)||info.carryTrot.feet.length!==info.carryTrot.frames.length||info.carryTrot.feet.some(list=>!Array.isArray(list)||list.some(f=>typeof f.id!=='string'||typeof f.stance!=='boolean'||![f.x,f.y].every(v=>Number.isFinite(v)&&v>=0&&v<320))))throw Error('Invalid carry metadata');}
37
+ for(const [motion,maxKeys,maxRecovery] of [[info.attentionPaw,8,.3],[info.attentionWall,12,.4]])if(motion){
38
+ validateMotion(motion,info.frames);
39
+ if(motion.frames[0]!==0||motion.frames.at(-1)!==0||!motion.releases||Object.keys(motion.releases).length>maxKeys)throw Error('Invalid attention endpoint');
40
+ for(const frame of new Set(motion.frames)){const exit=motion.releases[frame];validateMotion(exit,info.frames);if(exit.frames[0]!==frame||exit.frames.at(-1)!==0||exit.duration>maxRecovery)throw Error('Invalid attention recovery');}
41
+ }
36
42
  if(output!==undefined&&(!(output instanceof Uint8ClampedArray)||output.length!==320*320*4))throw Error('Invalid comfort output');
37
43
  const frames=grayFrameReader(bytes,320,320,info.frames);let rgba=output??null;
38
44
  if(rgba)for(let i=3;i<rgba.length;i+=4)rgba[i]=255;
39
45
  return pose=>{
40
- if(!['tug','sleep','scramble','pickup','carry-trot','carry-settle'].includes(pose?.clip))throw Error('Invalid comfort clip');
46
+ if(!['tug','sleep','scramble','pickup','carry-trot','carry-settle','attention-paw','attention-wall'].includes(pose?.clip))throw Error('Invalid comfort clip');
41
47
  if(!rgba){rgba=new Uint8ClampedArray(320*320*4);for(let i=3;i<rgba.length;i+=4)rgba[i]=255;}
42
48
  let index;
43
- if(['scramble','pickup','carry-trot','carry-settle'].includes(pose.clip)){
49
+ if(['scramble','pickup','carry-trot','carry-settle','attention-paw','attention-wall'].includes(pose.clip)){
44
50
  const motion=comfortMotion(pose,info);if(!motion)throw Error('Invalid scramble/pickup pose');
45
- index=motion.frames[comfortMotionKey(pose.progress,motion)];
51
+ index=motion.frames[comfortMotionKey(comfortProgress(pose),motion)];
46
52
  }else index=pose.clip==='sleep'?info.sleep?.[pose.variant]:comfortFrameIndex(pose.progress,info);
47
53
  if(!Number.isInteger(index))throw Error('Invalid sleep variant');
48
54
  const frame=frames.getFrame(index),base=pose.base,blend=pose.blend??1;
Binary file