@songsid/agend 2.3.2-beta.10 → 2.3.2-beta.12
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/README.md +4 -0
- package/README.zh-TW.md +4 -0
- package/dist/agent-cli.js +48 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +6 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +3 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/backend/kiro-classic-archive.d.ts +52 -0
- package/dist/backend/kiro-classic-archive.js +444 -0
- package/dist/backend/kiro-classic-archive.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +19 -0
- package/dist/backend/kiro-identity.js +43 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-migration-verify.d.ts +10 -0
- package/dist/backend/kiro-migration-verify.js +52 -0
- package/dist/backend/kiro-migration-verify.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.js +21 -1
- package/dist/backend/kiro-v2-store-worker.js.map +1 -1
- package/dist/backend/kiro-v2-store.d.ts +8 -0
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +99 -1
- package/dist/backend/kiro.js +352 -18
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +49 -0
- package/dist/backend/types.js +9 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/ipc-timeouts.js +3 -0
- package/dist/channel/ipc-timeouts.js.map +1 -1
- package/dist/channel/mcp-tools.js +12 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +2 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +45 -0
- package/dist/daemon.js +342 -19
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +15 -4
- package/dist/dashboard-permissions.js +49 -23
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-control-audit.d.ts +1 -1
- package/dist/fleet-control-audit.js +3 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-manager.d.ts +61 -2
- package/dist/fleet-manager.js +421 -28
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +10 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +28 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +28 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +20 -2
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +20 -2
- package/dist/instance-config-impact.js +2 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +4 -0
- package/dist/instance-lifecycle.js +7 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-migrate.d.ts +11 -0
- package/dist/kiro-migrate.js +86 -31
- package/dist/kiro-migrate.js.map +1 -1
- package/dist/kiro-session-proof.d.ts +41 -0
- package/dist/kiro-session-proof.js +89 -0
- package/dist/kiro-session-proof.js.map +1 -0
- package/dist/kiro-tui-forensic.d.ts +5 -0
- package/dist/kiro-tui-forensic.js +3 -3
- package/dist/kiro-tui-forensic.js.map +1 -1
- package/dist/locale.js +142 -0
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you.d.ts +3 -1
- package/dist/needs-you.js +4 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/office-share.d.ts +12 -2
- package/dist/office-share.js +27 -2
- package/dist/office-share.js.map +1 -1
- package/dist/outbound-schemas.d.ts +39 -0
- package/dist/outbound-schemas.js +29 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/remote-card-prompt.d.ts +38 -0
- package/dist/remote-card-prompt.js +86 -0
- package/dist/remote-card-prompt.js.map +1 -0
- package/dist/settings-change.js +8 -5
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-instance-patch.js +1 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/tool-permissions.js +17 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/transcript-sources.d.ts +1 -0
- package/dist/transcript-sources.js +2 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/ui/chat-store.js +3 -2
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-office-editor.js +54 -2
- package/dist/ui/fleet-office-layout.js +44 -7
- package/dist/ui/fleet-office-pack.d.ts +9 -3
- package/dist/ui/fleet-office-pack.js +117 -19
- package/dist/ui/fleet-office-store.js +43 -0
- package/dist/ui/fleet-office-zones.js +92 -17
- package/dist/ui/fleet-office.js +102 -16
- package/dist/ui/panel-chat.js +14 -2
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +1 -1
- package/dist/ui/panel-settings.js +5 -5
- package/dist/ui/settings-model.js +17 -9
- package/dist/ui/settings-strings.js +2 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-stream.js +1 -1
- package/dist/ui/shared/app.css +18 -2
- package/dist/web-api.d.ts +5 -1
- package/dist/web-api.js +91 -3
- package/dist/web-api.js.map +1 -1
- package/dist/web-office-acts.js +2 -2
- package/dist/web-office-acts.js.map +1 -1
- package/dist/web-office-pack-read.d.ts +35 -0
- package/dist/web-office-pack-read.js +252 -0
- package/dist/web-office-pack-read.js.map +1 -0
- package/dist/web-office-pack-tools.d.ts +232 -0
- package/dist/web-office-pack-tools.js +754 -0
- package/dist/web-office-pack-tools.js.map +1 -0
- package/dist/web-office-skins.d.ts +68 -4
- package/dist/web-office-skins.js +318 -33
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.d.ts +21 -2
- package/dist/web-office-store.js +78 -18
- package/dist/web-office-store.js.map +1 -1
- package/package.json +1 -1
- package/templates/windows-shortcuts.ps1 +16 -15
|
@@ -42,33 +42,62 @@ export function overflowSpots(area, need = 0) {
|
|
|
42
42
|
* slot }). Someone still in the same area keeps their slot; the others take the first free seat, in the area's order,
|
|
43
43
|
* then an overflow spot (slot -1, -2, …). No two share one. Whoever finds neither is counted in `crowd` (area → n).
|
|
44
44
|
* Returns { seats: Map name → { area, slot, x, y }, crowd: Map }.
|
|
45
|
+
*
|
|
46
|
+
* #2691: a seat may be reserved (its `for`). Whoever it is for, wanting its area, takes a free seat reserved for them
|
|
47
|
+
* before anything else (the one they sat on last time, else their first in the area's order) — owners are seated
|
|
48
|
+
* before anyone keeps a slot, so a borrower on an owner's seat moves off when the owner comes. Everyone else keeps
|
|
49
|
+
* their slot or takes the first free seat not reserved for someone else: a plain one, else a reserved one that says
|
|
50
|
+
* `lend` (one no owner of it took). A reserved seat not lent, none of whose owners wants the area, is unavailable:
|
|
51
|
+
* the area's overflow spots and `over` count without it. Seats without `for` work as before.
|
|
45
52
|
*/
|
|
46
53
|
export function assignSeats(pack, wants, prev = new Map()) {
|
|
47
|
-
const seats = new Map(), crowd = new Map(), taken = new Map(), spill = new Map(), demand = new Map();
|
|
54
|
+
const seats = new Map(), crowd = new Map(), taken = new Map(), spill = new Map(), demand = new Map(), wanting = new Map();
|
|
48
55
|
const areaOf = (a) => own(pack.areas, a);
|
|
49
|
-
for (const w of wants)
|
|
56
|
+
for (const w of wants) {
|
|
57
|
+
demand.set(w.area, (demand.get(w.area) || 0) + 1);
|
|
58
|
+
if (!wanting.has(w.area)) wanting.set(w.area, new Set());
|
|
59
|
+
wanting.get(w.area).add(w.name);
|
|
60
|
+
}
|
|
61
|
+
const mine = (s, name) => !!s.for && s.for.includes(name);
|
|
62
|
+
const lent = (s) => !!s.for && s.lend === true;
|
|
63
|
+
// The seats someone wanting the area may end up on: plain, lent, or reserved for someone who wants it.
|
|
64
|
+
const usable = (area) => areaOf(area).seats.filter((s) => !s.for || s.lend === true || s.for.some((n) => wanting.get(area).has(n))).length;
|
|
50
65
|
const spotsOf = (area) => {
|
|
51
|
-
if (!spill.has(area)) spill.set(area, overflowSpots(areaOf(area), Math.max(0, (demand.get(area) || 0) -
|
|
66
|
+
if (!spill.has(area)) spill.set(area, overflowSpots(areaOf(area), Math.max(0, (demand.get(area) || 0) - usable(area))));
|
|
52
67
|
return spill.get(area);
|
|
53
68
|
};
|
|
54
69
|
const pos = (area, slot) => (slot >= 0 ? areaOf(area).seats[slot] : spotsOf(area)[-slot - 1]);
|
|
55
70
|
const usedIn = (area) => { if (!taken.has(area)) taken.set(area, new Set()); return taken.get(area); };
|
|
56
71
|
const give = (name, area, slot) => { const p = pos(area, slot); usedIn(area).add(slot); seats.set(name, { area, slot, x: p.x, y: p.y }); };
|
|
72
|
+
const free = (w) => !seats.has(w.name) && !!areaOf(w.area);
|
|
73
|
+
// Owners first: their own seat from last time, then their first free one.
|
|
74
|
+
for (const w of wants) {
|
|
75
|
+
const p = prev.get(w.name), s = p && p.area === w.area && p.slot >= 0 && areaOf(w.area) ? areaOf(w.area).seats[p.slot] : null;
|
|
76
|
+
if (s && mine(s, w.name) && !usedIn(w.area).has(p.slot)) give(w.name, w.area, p.slot);
|
|
77
|
+
}
|
|
78
|
+
for (const w of wants) {
|
|
79
|
+
if (!free(w)) continue;
|
|
80
|
+
const used = usedIn(w.area), slot = areaOf(w.area).seats.findIndex((s, i) => mine(s, w.name) && !used.has(i));
|
|
81
|
+
if (slot >= 0) give(w.name, w.area, slot);
|
|
82
|
+
}
|
|
57
83
|
for (const w of wants) {
|
|
58
84
|
const p = prev.get(w.name);
|
|
59
|
-
if (!p || p.area !== w.area || !
|
|
85
|
+
if (!free(w) || !p || p.area !== w.area || !pos(w.area, p.slot) || usedIn(w.area).has(p.slot)) continue;
|
|
86
|
+
const s = p.slot >= 0 ? areaOf(w.area).seats[p.slot] : null;
|
|
87
|
+
if (s && s.for && !lent(s)) continue; // reserved for someone else (theirs would be taken above)
|
|
60
88
|
give(w.name, w.area, p.slot);
|
|
61
89
|
}
|
|
62
90
|
for (const w of wants) {
|
|
63
|
-
if (
|
|
91
|
+
if (!free(w)) continue;
|
|
64
92
|
const used = usedIn(w.area), area = areaOf(w.area);
|
|
65
|
-
let slot = area.seats.findIndex((
|
|
93
|
+
let slot = area.seats.findIndex((s, i) => !s.for && !used.has(i));
|
|
94
|
+
if (slot < 0) slot = area.seats.findIndex((s, i) => lent(s) && !used.has(i));
|
|
66
95
|
if (slot < 0) { const j = spotsOf(w.area).findIndex((_, i) => !used.has(-i - 1)); slot = j < 0 ? null : -j - 1; }
|
|
67
96
|
if (slot === null) crowd.set(w.area, (crowd.get(w.area) || 0) + 1);
|
|
68
97
|
else give(w.name, w.area, slot);
|
|
69
98
|
}
|
|
70
|
-
// Over capacity: more people want the area than it has seats (their bubbles and tags are thinned, fleet-office.js).
|
|
71
|
-
const over = new Set([...demand].filter(([a, n]) => areaOf(a) && n >
|
|
99
|
+
// Over capacity: more people want the area than it has seats for them (their bubbles and tags are thinned, fleet-office.js).
|
|
100
|
+
const over = new Set([...demand].filter(([a, n]) => areaOf(a) && n > usable(a)).map(([a]) => a));
|
|
72
101
|
return { seats, crowd, over };
|
|
73
102
|
}
|
|
74
103
|
|
|
@@ -79,11 +108,33 @@ export function assignSeats(pack, wants, prev = new Map()) {
|
|
|
79
108
|
*/
|
|
80
109
|
export function zonesLayout(pack, nodes, prev = new Map(), { night = false } = {}) {
|
|
81
110
|
// At night (#2568) the idle go to bed: the area asleep people go to while it has a free seat; the rest doze where they are.
|
|
82
|
-
const bed = zoneOf(pack, "paused"),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
111
|
+
const bed = zoneOf(pack, "paused"), bedSeats = own(pack.areas, bed)?.seats || [], sleepers = new Set(), inBed = new Set();
|
|
112
|
+
const wantsOf = (beds) => nodes.filter((n) => n.state !== "missing").map((n) => ({ name: n.name, area: beds.has(n.name) ? bed : zoneOf(pack, n.state) }));
|
|
113
|
+
let placed = assignSeats(pack, wantsOf(inBed), prev);
|
|
114
|
+
if (night) {
|
|
115
|
+
// #2691 review: who goes to bed is decided by the seat assignment itself (assignSeats' reservation rules), one
|
|
116
|
+
// sleeper at a time — whoever has a bed of their own there first, then the rest in the fleet's order. A sleeper
|
|
117
|
+
// goes to bed only if it gives them a bed (never an overflow spot) without taking one from anyone already in one
|
|
118
|
+
// (the awake first, then the sleepers admitted before), unless it is a bed reserved for them: an owner comes first,
|
|
119
|
+
// as by day. Everyone else dozes where they are.
|
|
120
|
+
const inABed = (r) => new Set([...r.seats].filter(([, s]) => s.area === bed && s.slot >= 0).map(([name]) => name));
|
|
121
|
+
const idle = nodes.filter((n) => n.state === "idle");
|
|
122
|
+
const hasBed = (n) => bedSeats.some((s) => s.for && s.for.includes(n.name));
|
|
123
|
+
for (const n of idle) sleepers.add(n.name);
|
|
124
|
+
for (const n of [...idle.filter(hasBed), ...idle.filter((n) => !hasBed(n))]) {
|
|
125
|
+
const trial = assignSeats(pack, wantsOf(new Set([...inBed, n.name])), prev), s = trial.seats.get(n.name);
|
|
126
|
+
if (!s || s.area !== bed || s.slot < 0) {
|
|
127
|
+
if (!hasBed(n)) break; // no bed free for anyone without one: none for the rest either
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const before = inABed(placed), after = inABed(trial);
|
|
131
|
+
const theirs = !!bedSeats[s.slot].for && bedSeats[s.slot].for.includes(n.name);
|
|
132
|
+
if (!theirs && [...before].some((m) => !after.has(m))) continue;
|
|
133
|
+
inBed.add(n.name);
|
|
134
|
+
placed = trial;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const { seats, crowd, over } = placed;
|
|
87
138
|
const desks = nodes.filter((n) => seats.has(n.name)).map((n) => {
|
|
88
139
|
const s = seats.get(n.name);
|
|
89
140
|
return { key: `zone/${n.name}`, node: n, x: s.x - 24, y: s.y - 52, w: 48, h: 66, seat: { x: s.x, y: s.y }, wp: null, area: s.area, slot: s.slot, night: sleepers.has(n.name) };
|
|
@@ -386,14 +437,38 @@ export function linesFor(state, lang, pack, defaults) {
|
|
|
386
437
|
|
|
387
438
|
// ── Furniture ──
|
|
388
439
|
|
|
389
|
-
/**
|
|
390
|
-
|
|
440
|
+
/**
|
|
441
|
+
* The people an item with `activeFor` is active for now (#2570): those of them shown in an `activeWhen` state — with
|
|
442
|
+
* "activeOn": "seated" (#2698), only those `posture(name)` says are "seated" (at the seat zones gave them, not walking).
|
|
443
|
+
* `posture` null: no walkers, everyone at their seat.
|
|
444
|
+
*/
|
|
445
|
+
function activeForNow(f, byName, posture) {
|
|
446
|
+
const seated = (n) => f.activeOn !== "seated" || !posture || posture(n) === "seated";
|
|
447
|
+
return (f.activeFor || [...byName.keys()]).filter((n) => byName.has(n) && f.activeWhen.includes(byName.get(n)) && seated(n));
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* The furniture active now: an item whose `activeWhen` names a state someone shown is in. With activeFor (#2570),
|
|
451
|
+
* only those people's states count; with "activeOn": "seated" (#2698), only once one of them is seated (`posture`,
|
|
452
|
+
* name → "walking" | "seated" | "standing"; null: everyone seated) — it turns back as they walk off.
|
|
453
|
+
*/
|
|
454
|
+
export function activeFurniture(pack, states, byName = new Map(), posture = null) {
|
|
391
455
|
const on = new Set();
|
|
392
456
|
for (const f of (pack && pack.furniture) || []) {
|
|
393
457
|
if (!f.activeWhen) continue;
|
|
394
|
-
|
|
395
|
-
const hit = f.activeFor ? f.activeFor.some((n) => byName.has(n) && f.activeWhen.includes(byName.get(n))) : f.activeWhen.some((s) => states.has(s));
|
|
458
|
+
const hit = f.activeFor || f.activeOn === "seated" ? activeForNow(f, byName, posture).length > 0 : f.activeWhen.some((s) => states.has(s));
|
|
396
459
|
if (hit) on.add(f.id);
|
|
397
460
|
}
|
|
398
461
|
return on;
|
|
399
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* #2698: who is drawn on an item instead of themselves (name → the item's id): for each `active` item with "hides",
|
|
465
|
+
* the people it is active for (activeFor's order) who are not walking — someone walking is always drawn. One item each.
|
|
466
|
+
*/
|
|
467
|
+
export function hiddenBy(pack, active, byName = new Map(), posture = null) {
|
|
468
|
+
const out = new Map();
|
|
469
|
+
for (const f of (pack && pack.furniture) || []) {
|
|
470
|
+
if (!f.hides || !f.activeWhen || !active.has(f.id)) continue;
|
|
471
|
+
for (const n of activeForNow(f, byName, posture)) if (!out.has(n) && (!posture || posture(n) !== "walking")) out.set(n, f.id);
|
|
472
|
+
}
|
|
473
|
+
return out;
|
|
474
|
+
}
|
package/dist/ui/fleet-office.js
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
// goes through the same view transform.
|
|
16
16
|
// - Speech bubbles: now and then a short line for its state (typed out; at once with reduced motion) — the page's own
|
|
17
17
|
// lines or the skin's, never anything an agent said. Furniture can react: an item with `activeWhen` animates while
|
|
18
|
-
// anyone shown is in that state
|
|
18
|
+
// anyone shown is in that state — or (#2698, "activeOn": "seated") once its person sits down; one that "hides" its
|
|
19
|
+
// people carries their name tags and bubbles while it is active, and hovering it hits them (furnitureAt).
|
|
19
20
|
// - Each occupant is a little pixel person in a pose that loops a few frames: working (typing, the lamp on), idle (tea),
|
|
20
21
|
// paused or stopped (asleep, a closed sign), needs you / crashed / looks stuck (a hand up, a blinking light).
|
|
21
22
|
// - Traffic: each new message, delegation or report is an item carried from the sender's desk to the receiver's along
|
|
@@ -64,13 +65,13 @@ import { hits, needle } from "./fleet-canvas-layout.js";
|
|
|
64
65
|
import { AUTO_ID, LOBBY, autoLayout, backingScale, coatOf, createFlights, customLayout, deskAt, desksOf, filterOk, lampOf, layoutOk, lightOf, membersOf,
|
|
65
66
|
officePose, officeRooms, signOf, workOn } from "./fleet-office-model.js";
|
|
66
67
|
import { BUILT_IN_SKINS, CHAR_DEPTH, DEFAULT_PAL, bubbleBox, loadCozySkin, loadPackSkin, modeOf, skinFor, tagBox } from "./fleet-office-skin.js";
|
|
67
|
-
import { CALLING, activeFurniture, bubbleAt, bubbleRank, NIGHT, WANDER, createMail, createWalkers, isNight, linesFor, npcKey, personAt, placeBubbles, thinBubbles, walkTargets, zonesLayout } from "./fleet-office-zones.js";
|
|
68
|
+
import { CALLING, activeFurniture, bubbleAt, hiddenBy, bubbleRank, NIGHT, WANDER, createMail, createWalkers, isNight, linesFor, npcKey, personAt, placeBubbles, thinBubbles, walkTargets, zonesLayout } from "./fleet-office-zones.js";
|
|
68
69
|
import { actsOk, createOfficeStore } from "./fleet-office-store.js";
|
|
69
70
|
import { POLL_MS } from "./fleet-traffic.js";
|
|
70
71
|
import { OfficeEditor, officeForSave } from "./fleet-office-editor.js";
|
|
71
72
|
import { LayoutEditor, handlesOf } from "./fleet-office-layout.js";
|
|
72
73
|
import { ShareButton } from "./office-share-dialog.js";
|
|
73
|
-
import { applyOverrides, fitNight } from "./fleet-office-pack.js";
|
|
74
|
+
import { applyOverrides, fitNight, fitSeatFor, stripBox } from "./fleet-office-pack.js";
|
|
74
75
|
import { ALL_ID, LOBBY_TILE, OV, createCrossFlights, cropToRooms, fromTile, inTile, labelAt, lobbyNodes, overviewOk, tileAt, tileBox, tileLayout, visibleTiles } from "./fleet-office-overview.js";
|
|
75
76
|
import { centreOn, gestureState, gestures, miniPoint, toScreen, toWorld, wheelFactor, zoomKey, zoomTo } from "./fleet-canvas-view.js";
|
|
76
77
|
import { OK_MAX, OK_MIN, clampPan, desksK, edgeMatte, edgeStrips, fitScene, layerScale, miniBox, miniFrame, openingView, pinchOffice, savedOf, stepK, viewFromSaved,
|
|
@@ -193,7 +194,7 @@ function looksOf(L, work, q, mode, zones) {
|
|
|
193
194
|
function withOverrides(baseSkin, zones, office) {
|
|
194
195
|
const ov = zones && office ? office.overrides : null;
|
|
195
196
|
if (!ov || ov.pack !== baseSkin.id) return { skin: baseSkin, ignored: false };
|
|
196
|
-
const a = applyOverrides(baseSkin.pack, fitNight(baseSkin.pack, ov)); // #2665: a night place the pack lost: left out
|
|
197
|
+
const a = applyOverrides(baseSkin.pack, fitSeatFor(baseSkin.pack, fitNight(baseSkin.pack, ov))); // #2665/#2691: a night place or seat the pack lost: left out
|
|
197
198
|
return a.ok ? { skin: { ...baseSkin, pack: a.pack }, ignored: false } : { skin: baseSkin, ignored: true };
|
|
198
199
|
}
|
|
199
200
|
/** A stored office's scene (or the lobby tile's): seats in its style's areas, its own seats, or one room wrapping at `deskW`. */
|
|
@@ -203,7 +204,7 @@ function officeScene(office, nodes, skin, zones, deskW, prevSeats, night = false
|
|
|
203
204
|
return lay ? customLayout({ ...office, layout: lay }, nodes) : autoLayout([{ key: office.id, name: office.name, lobby: !!office.lobby, nodes }], { width: deskW });
|
|
204
205
|
}
|
|
205
206
|
/** What drawPart needs of a scene; its layers are painted on first draw (and again at another resolution). */
|
|
206
|
-
function partOf(L, looks, skin, zones, walkers, flights) {
|
|
207
|
+
export function partOf(L, looks, skin, zones, walkers, flights) {
|
|
207
208
|
const all = L.rooms.flatMap((r) => r.desks);
|
|
208
209
|
const shown = new Set([...looks.values()].map((l) => l.state)), byName = new Map([...looks.values()].filter((l) => l.name).map((l) => [l.name, l.state]));
|
|
209
210
|
return { L, looks, skin, zones, walkers, flights, all, desks: all.filter((d) => d.node), layers: null,
|
|
@@ -214,6 +215,43 @@ function partOf(L, looks, skin, zones, walkers, flights) {
|
|
|
214
215
|
* (Edit layout, #2665: applyOverrides puts it on `night`), else the pack's night x/y, else its (moved) day place.
|
|
215
216
|
*/
|
|
216
217
|
export const nightFurniture = (list) => list.map((f) => (f.night ? { ...f, sprite: f.night.sprite, activeSprite: null, x: f.night.x ?? f.x, y: f.night.y ?? f.y, asleep: true } : f));
|
|
218
|
+
/** #2698: where someone is at `t` against their seat: "walking"; "seated" (still, at the seat zones gave them — or no
|
|
219
|
+
* walker yet: drawn there); "standing" (still elsewhere: a stroll's spot, an act's point, the seat left before a walk). */
|
|
220
|
+
const postureAt = (p, t) => {
|
|
221
|
+
const seats = new Map(p.all.filter((d) => d.node).map((d) => [d.node.name, d.seat]));
|
|
222
|
+
return (name) => {
|
|
223
|
+
const at = p.walkers && p.walkers.at(name, t), s = seats.get(name);
|
|
224
|
+
if (!at) return "seated";
|
|
225
|
+
if (at.walking) return "walking";
|
|
226
|
+
return s && Math.abs(at.x - s.x) < 1 && Math.abs(at.y - s.y) < 1 ? "seated" : "standing";
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* The furniture active at `t` and who is drawn on an item instead of themselves (`hidden`: name → item id). A pack
|
|
231
|
+
* with no "activeOn": "seated" nor "hides" (#2698): the scene's `active`, as before, and nobody hidden; else worked out
|
|
232
|
+
* from where everyone is at `t` (their walkers). An item asleep at bedtime (a `night`) hides nobody.
|
|
233
|
+
*/
|
|
234
|
+
export function furnitureAt(p, t) {
|
|
235
|
+
const pk = p.zones && p.skin && p.skin.pack, list = (pk && pk.furniture) || [];
|
|
236
|
+
if (!list.some((f) => f.activeOn === "seated" || f.hides)) return { active: p.active || new Set(), hidden: new Map() };
|
|
237
|
+
const looks = [...p.looks.values()], byName = new Map(looks.filter((l) => l.name).map((l) => [l.name, l.state]));
|
|
238
|
+
const posture = postureAt(p, t), active = activeFurniture(pk, new Set(looks.map((l) => l.state)), byName, posture);
|
|
239
|
+
const hidden = hiddenBy(pk, active, byName, posture);
|
|
240
|
+
if (p.night) for (const [n, id] of [...hidden]) if (list.some((f) => f.id === id && f.night)) hidden.delete(n);
|
|
241
|
+
return { active, hidden };
|
|
242
|
+
}
|
|
243
|
+
/** #2698: the box an item hiding its people is hit in — its active picture, down to its name tags' row. */
|
|
244
|
+
const hidingBox = (f) => {
|
|
245
|
+
const b = stripBox(f.activeSprite || f.sprite, f.x, f.y);
|
|
246
|
+
return { ...b, h: Math.max(b.h, f.y + 17 - b.y) };
|
|
247
|
+
};
|
|
248
|
+
/** #2698: name tags in one row centred on (x, y) — each { look, x } (its centre), TAG_GAP apart. */
|
|
249
|
+
const TAG_GAP = 4;
|
|
250
|
+
export function tagRow(ctx, x, y, looks) {
|
|
251
|
+
const ws = looks.map((l) => tagBox(ctx, x, y, l.label).w), total = ws.reduce((a, w) => a + w, 0) + TAG_GAP * Math.max(0, ws.length - 1);
|
|
252
|
+
let left = x - total / 2;
|
|
253
|
+
return looks.map((look, i) => { const cx = Math.round(left + ws[i] / 2); left += ws[i] + TAG_GAP; return { look, x: cx }; });
|
|
254
|
+
}
|
|
217
255
|
/** #2596: each NPC with a `walk` where its walker is now (`at(key)`): moved there — its tag and depth with it — and, while
|
|
218
256
|
* it walks, `walking` and its `heading` (its walk strip: walkStrip); asleep, or with no walker (not strolling), where it is. */
|
|
219
257
|
export const strollFurniture = (list, at) => list.map((f) => {
|
|
@@ -309,7 +347,7 @@ function paintLayers(p, env, ls, tagsOff = false) {
|
|
|
309
347
|
* hovered (its name tag shows even in a crowd); `tagsOff` (#2554: a tile too small to read them): the name tags show
|
|
310
348
|
* only for the one hovered and the Find box's matches. Whether anything in it is moving (in the air, or someone walking).
|
|
311
349
|
*/
|
|
312
|
-
function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer = null) {
|
|
350
|
+
export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer = null) {
|
|
313
351
|
const sk = p.skin;
|
|
314
352
|
const ls = layerScale(a, p.L.width, p.L.height);
|
|
315
353
|
if (!p.layers || p.layers.ls !== ls || p.layers.tagsOff !== tagsOff) p.layers = paintLayers(p, env, ls, tagsOff);
|
|
@@ -326,7 +364,7 @@ function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer
|
|
|
326
364
|
// Furniture under the people; the people (nearest last) with the furniture whose depth is "y" among them, by base
|
|
327
365
|
// y (a tie: the item first, the person in front); furniture over them; things in the air; the pack's foreground
|
|
328
366
|
// (door frames, pillars, table fronts); then what must stay readable: crowd counts and name tags (bubbles below).
|
|
329
|
-
const t = ms > 0 ? ms : now();
|
|
367
|
+
const t = ms > 0 ? ms : now(), live = furnitureAt(p, t);
|
|
330
368
|
const people = p.desks.map((d) => {
|
|
331
369
|
const at = p.walkers.at(d.node.name, t) || { ...d.seat, walking: false };
|
|
332
370
|
if (at.walking) walking = true;
|
|
@@ -339,12 +377,13 @@ function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer
|
|
|
339
377
|
// #2665: by day, each item's night place too (at night the items are there already).
|
|
340
378
|
...(p.ghosts && !p.night ? nightGhostsOf(sk.pack).map((n) => ({ y: n.y, k: 1, n })) : [])]
|
|
341
379
|
.sort((x, y) => x.y - y.y || x.k - y.k);
|
|
342
|
-
for (const f of furniture) if (f.depth !== "y" && f.depth < CHAR_DEPTH) sk.drawFurniture(ctx, f, f.asleep ||
|
|
380
|
+
for (const f of furniture) if (f.depth !== "y" && f.depth < CHAR_DEPTH) sk.drawFurniture(ctx, f, f.asleep || live.active.has(f.id), ms, env);
|
|
343
381
|
for (const it of post) if (it.t === 0) sk.drawProp(ctx, it, ms, env); // a letter on the floor: under the people
|
|
344
382
|
for (const { f, p: q, g, n } of band) {
|
|
345
|
-
if (f) { sk.drawFurniture(ctx, f, f.asleep ||
|
|
383
|
+
if (f) { sk.drawFurniture(ctx, f, f.asleep || live.active.has(f.id), ms, env); continue; }
|
|
346
384
|
if (n) { ctx.save(); ctx.globalAlpha = GHOST_ALPHA; sk.drawFurniture(ctx, n, true, 0, env); ctx.restore(); continue; }
|
|
347
385
|
if (g) { ctx.save(); ctx.globalAlpha = GHOST_ALPHA; sk.drawCharacter(ctx, { x: g.x - 44, y: g.y - 53, w: 88, h: 53, seat: g }, g.look, 0, env); ctx.restore(); continue; }
|
|
386
|
+
if (live.hidden.has(q.look.name)) continue; // #2698: drawn as the item (never while walking)
|
|
348
387
|
const box = { ...q.d, x: q.at.x - 44, y: q.at.y - 53, w: 88, h: 53, seat: { x: q.at.x, y: q.at.y } };
|
|
349
388
|
// An agent's own pose (office_set, #2578) while its act lasts and nothing calls it; walking is walking.
|
|
350
389
|
const posed = actPosed(p, q.look);
|
|
@@ -352,7 +391,7 @@ function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer
|
|
|
352
391
|
sk.drawCharacter(ctx, box, look, ms, env);
|
|
353
392
|
if (!q.at.walking) speakers.push({ look: q.look, ...speakerAt(sk, look, q.at), area: q.d.area });
|
|
354
393
|
}
|
|
355
|
-
for (const f of furniture) if (f.depth !== "y" && f.depth >= CHAR_DEPTH) sk.drawFurniture(ctx, f, f.asleep ||
|
|
394
|
+
for (const f of furniture) if (f.depth !== "y" && f.depth >= CHAR_DEPTH) sk.drawFurniture(ctx, f, f.asleep || live.active.has(f.id), ms, env);
|
|
356
395
|
for (const it of up) sk.drawProp(ctx, it, ms, env);
|
|
357
396
|
for (const it of post) if (it.t > 0) sk.drawProp(ctx, it, ms, env);
|
|
358
397
|
if (sk.drawForeground) sk.drawForeground(ctx, p.L, env);
|
|
@@ -360,13 +399,38 @@ function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer
|
|
|
360
399
|
for (const [ar, n] of p.L.crowd || []) if (p.L.areas[ar] && sk.drawCrowd) sk.drawCrowd(ctx, p.L.areas[ar], n, env);
|
|
361
400
|
// In an area over capacity the name tags show on hover or focus only (#2513: a crowd's tags covered each other).
|
|
362
401
|
const over = p.L.over || new Set();
|
|
402
|
+
const tagged = (q) => !((tagsOff || over.has(q.d.area)) && q.d.key !== hot && !q.look.match);
|
|
363
403
|
for (const q of people) {
|
|
364
|
-
if (
|
|
404
|
+
if (live.hidden.has(q.look.name) || !tagged(q)) continue;
|
|
365
405
|
sk.drawTag(ctx, q.at.x, q.at.y, q.look, env);
|
|
366
406
|
tags.push(tagBox(ctx, q.at.x, q.at.y, q.look.label));
|
|
367
407
|
}
|
|
368
408
|
// A furniture item's own name (#2574: an NPC drawn as furniture), tagged like a person; none on a tile too small to read.
|
|
369
|
-
|
|
409
|
+
// #2698: an item hiding its people carries their tags too, in one row at its tag point (its own first; each keeps
|
|
410
|
+
// its place in the row while another's tag is left out), and their bubbles, over its picture. The tags drawn are
|
|
411
|
+
// kept on the scene (`heldTags`): hitPart hits each person on theirs, past the picture's edge too (#2699 review).
|
|
412
|
+
const held = [];
|
|
413
|
+
for (const f of furniture) {
|
|
414
|
+
const on = people.filter((q) => live.hidden.get(q.look.name) === f.id);
|
|
415
|
+
if (!on.length) {
|
|
416
|
+
if (!tagsOff && f.label) { sk.drawTag(ctx, f.x, f.y, { label: f.label }, env); tags.push(tagBox(ctx, f.x, f.y, f.label)); }
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const order = new Map((f.activeFor || []).map((n, i) => [n, i]));
|
|
420
|
+
on.sort((x, y) => (order.get(x.look.name) ?? 0) - (order.get(y.look.name) ?? 0));
|
|
421
|
+
const row = tagRow(ctx, f.x, f.y, [...(f.label ? [{ label: f.label }] : []), ...on.map((q) => q.look)]);
|
|
422
|
+
const top = f.sprite ? Math.min(f.y - 50, stripBox(f.activeSprite || f.sprite, f.x, f.y).y + 2) : f.y - 50;
|
|
423
|
+
for (const [i, r] of row.entries()) {
|
|
424
|
+
const q = f.label ? on[i - 1] : on[i];
|
|
425
|
+
if (q) speakers.push({ look: q.look, x: r.x + 10, y: top, area: q.d.area });
|
|
426
|
+
if (q ? !tagged(q) : tagsOff) continue;
|
|
427
|
+
sk.drawTag(ctx, r.x, f.y, r.look, env);
|
|
428
|
+
const box = tagBox(ctx, r.x, f.y, r.look.label);
|
|
429
|
+
tags.push(box);
|
|
430
|
+
if (q) held.push({ name: q.look.name, id: f.id, box });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
p.heldTags = held;
|
|
370
434
|
} else {
|
|
371
435
|
for (const d of p.desks) { const look = p.looks.get(d.key); sk.drawCharacter(ctx, d, actPosed(p, look), ms, env); speakers.push({ look, x: d.x + 58, y: d.y + 12 }); }
|
|
372
436
|
blit(p.layers.front);
|
|
@@ -398,10 +462,30 @@ function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false, defer
|
|
|
398
462
|
* The person at (x, y) in a scene's units, as it draws them: on zones where they walk, in the box the skin draws them
|
|
399
463
|
* in (an uploaded character's strip, anchor and size: #2513 review), nearest first; on desks, the desk.
|
|
400
464
|
*/
|
|
401
|
-
function hitPart(p, x, y, t) {
|
|
465
|
+
export function hitPart(p, x, y, t) {
|
|
402
466
|
if (!p.zones) return deskAt(p.L, x, y);
|
|
403
|
-
|
|
404
|
-
|
|
467
|
+
// #2698: someone an item hides is hit on it, after everyone drawn as themselves: on their tag as drawPart last drew
|
|
468
|
+
// it (the row can run past the picture: a long label, long names), else on its picture and the tag row under it
|
|
469
|
+
// (several share its width, left to right in the row's order).
|
|
470
|
+
const { hidden } = furnitureAt(p, t);
|
|
471
|
+
const d = personAt(hidden.size ? p.all.filter((dk) => !dk.node || !hidden.has(dk.node.name)) : p.all, x, y, (dk) => p.walkers.at(dk.node.name, t),
|
|
472
|
+
(dk, at) => (p.skin.charBox && p.looks.get(dk.key) ? p.skin.charBox(p.looks.get(dk.key), at) : null));
|
|
473
|
+
if (d || !hidden.size) return d;
|
|
474
|
+
for (const h of [...(p.heldTags || [])].reverse()) {
|
|
475
|
+
const b = h.box;
|
|
476
|
+
if (hidden.get(h.name) !== h.id || x < b.x || x >= b.x + b.w || y < b.y || y >= b.y + b.h) continue;
|
|
477
|
+
return p.all.find((dk) => dk.node && dk.node.name === h.name) || null;
|
|
478
|
+
}
|
|
479
|
+
const sk = p.skin.pack, drawn = strollFurniture(p.night ? nightFurniture(sk.furniture) : sk.furniture, (k) => p.walkers.at(k, t));
|
|
480
|
+
for (const f of [...drawn].reverse()) {
|
|
481
|
+
if (!f.sprite || ![...hidden.values()].includes(f.id)) continue;
|
|
482
|
+
const b = hidingBox(f);
|
|
483
|
+
if (x < b.x || x >= b.x + b.w || y < b.y || y >= b.y + b.h) continue;
|
|
484
|
+
const order = new Map((f.activeFor || []).map((n, i) => [n, i]));
|
|
485
|
+
const on = p.all.filter((dk) => dk.node && hidden.get(dk.node.name) === f.id).sort((m, n) => (order.get(m.node.name) ?? 0) - (order.get(n.node.name) ?? 0));
|
|
486
|
+
return on[Math.min(on.length - 1, Math.floor(((x - b.x) / b.w) * on.length))] || null;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
405
489
|
}
|
|
406
490
|
|
|
407
491
|
function People({ nodes, pinned, onChange }) {
|
|
@@ -1113,6 +1197,8 @@ export function Office({ view, traffic, q = "", lease, onOpen, assetUrl, assetIm
|
|
|
1113
1197
|
setTip(null);
|
|
1114
1198
|
};
|
|
1115
1199
|
const closeLayout = () => setLayout(null);
|
|
1200
|
+
// #2691: whom a seat can be reserved for — the office's members (its teams' too).
|
|
1201
|
+
const layoutMembers = useMemo(() => (layout && custom ? membersOf(custom, view).map((n) => n.name) : []), [layout && layout.id, custom, view]);
|
|
1116
1202
|
const onDraft = (draft) => setLayout((cur) => (cur ? { ...cur, draft } : cur));
|
|
1117
1203
|
// Save: the session's list, this office's overrides replaced (none: back to the style's own), with the session's rev.
|
|
1118
1204
|
// A change made meanwhile (409) reads the list again: the session takes it (the view too), the draft is kept, and
|
|
@@ -1193,7 +1279,7 @@ export function Office({ view, traffic, q = "", lease, onOpen, assetUrl, assetIm
|
|
|
1193
1279
|
: html`<button key=${tl.key} ref=${at} type="button" class="oo-tile-label" data-office=${tl.key} title=${to("openOffice", tl.name)} aria-label=${to("openOffice", tl.name)}
|
|
1194
1280
|
onClick=${() => pick(tl.key)}>${text}</button>`;
|
|
1195
1281
|
}) : null}
|
|
1196
|
-
${layout ? html`<${LayoutEditor} key=${layout.id} base=${layout.base} start=${layout.start} place=${placeOverlay} dock=${!fs}
|
|
1282
|
+
${layout ? html`<${LayoutEditor} key=${layout.id} base=${layout.base} start=${layout.start} place=${placeOverlay} dock=${!fs} members=${layoutMembers}
|
|
1197
1283
|
stale=${layoutStale ? t("orgl.stale") : ""} onChange=${onDraft} onSave=${saveLayout} onClose=${closeLayout} />` : null}
|
|
1198
1284
|
${tipText ? html`<div ref=${tipRef} class="oo-tip" role="tooltip">
|
|
1199
1285
|
<strong>${tipText.primary}</strong>${tipText.secondary ? html` <span class="muted mono">${tipText.secondary}</span>` : null}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -618,8 +618,20 @@ function Prompts({ name }) {
|
|
|
618
618
|
if (!list.length) return null;
|
|
619
619
|
return html`<div data-trust="permission" class="prompts" aria-live="polite">${list.map(p => html`<div key=${p.nonce} class=${`prompt${p.resolved ? " done" : ""}`} role="group" aria-label=${p.text}>
|
|
620
620
|
<div class="txt">${p.resolved ? (p.outcome || t("chat.promptGone")) : textLinkSegments(p.text, p.links).map(sg => sg.href ? html`<a data-trust="link" class="st-link" href=${sg.href}>${sg.text}</a>` : sg.text)}</div>
|
|
621
|
-
${p.resolved ? null : html`<div class="acts">${p.actions.map(a => html`<button key=${a.id} type="button" class="btn" disabled=${!!p.busy} onClick=${() => store.answerPrompt(p, a.id)}>${a.label}</button>`)}
|
|
622
|
-
${p.busy ? html`<span class="wait">${t("chat.promptAnswering")}</span>` : null}</div>`}
|
|
621
|
+
${p.resolved ? null : html`<div class="acts">${p.actions.filter(a => !a.input).map(a => html`<button key=${a.id} type="button" class="btn" disabled=${!!p.busy} onClick=${() => store.answerPrompt(p, a.id)}>${a.label}</button>`)}
|
|
622
|
+
${p.busy ? html`<span class="wait">${t("chat.promptAnswering")}</span>` : null}</div>`}
|
|
623
|
+
${p.resolved ? null : p.actions.filter(a => a.input).map(a => html`<${PromptText} key=${a.id} p=${p} action=${a} />`)}</div>`)}</div>`;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** #2675: a prompt's typed answer (a kiro question card's "answer in words"): a box and its send button. */
|
|
627
|
+
function PromptText({ p, action }) {
|
|
628
|
+
const [text, setText] = useState("");
|
|
629
|
+
const send = (e) => { e.preventDefault(); if (text.trim() && !p.busy) store.answerPrompt(p, action.id, text); };
|
|
630
|
+
return html`<form class="prompt-text" onSubmit=${send}>
|
|
631
|
+
<input type="text" maxlength="4000" aria-label=${action.label} placeholder=${t("chat.promptTextPlaceholder")} value=${text} disabled=${!!p.busy}
|
|
632
|
+
onInput=${(e) => setText(e.currentTarget.value)} />
|
|
633
|
+
<button type="submit" class="btn" disabled=${!!p.busy || !text.trim()}>${t("chat.promptTextSend")}</button>
|
|
634
|
+
</form>`;
|
|
623
635
|
}
|
|
624
636
|
|
|
625
637
|
/** "<name> is working… 0:42", "looks stuck", "Stopping…", or what it waits on at its terminal. Text only. */
|
package/dist/ui/panel-log.js
CHANGED
|
@@ -55,7 +55,7 @@ register("log", {
|
|
|
55
55
|
cat_read: "Read", cat_edit: "Edit", cat_search: "Search", cat_run: "Run", cat_web: "Web", cat_mcp: "MCP", cat_agent: "Sub-agent",
|
|
56
56
|
cat_plan: "Plan", cat_message: "Message", cat_other: "Tool", plan_items: "{0} items",
|
|
57
57
|
ev_crash_respawn: "Crashed and restarted", ev_crash_loop: "Keeps crashing", ev_hang_detected: "Looks stuck", ev_hang_action: "Stuck: acted on",
|
|
58
|
-
ev_instance_paused: "Paused", ev_instance_resumed: "Resumed", ev_interactive_prompt: "Waiting at a prompt", ev_interactive_prompt_assist: "Prompt: asked for help",
|
|
58
|
+
ev_instance_paused: "Paused", ev_instance_resumed: "Resumed", ev_interactive_prompt: "Waiting at a prompt", ev_interactive_prompt_assist: "Prompt: asked for help", ev_remote_card: "Asked a question (posted to chat)", ev_remote_card_answer: "Question answered from chat",
|
|
59
59
|
ev_dialog_parked: "A dialog waits", ev_model_failover: "Switched to another model", ev_model_recovered: "Model back",
|
|
60
60
|
ev_mcp_died: "Tool server stopped", ev_mcp_recovered: "Tool server back", ev_mcp_auto_restart: "Tool server restarted",
|
|
61
61
|
ev_message_failed: "A message was not delivered", ev_pty_error: "Terminal error", ev_pty_recovered: "Terminal recovered",
|
|
@@ -93,7 +93,7 @@ register("log", {
|
|
|
93
93
|
cat_read: "讀取", cat_edit: "編輯", cat_search: "搜尋", cat_run: "執行", cat_web: "網頁", cat_mcp: "MCP", cat_agent: "子 agent",
|
|
94
94
|
cat_plan: "計畫", cat_message: "訊息", cat_other: "工具", plan_items: "{0} 項",
|
|
95
95
|
ev_crash_respawn: "當機後重新啟動", ev_crash_loop: "反覆當機", ev_hang_detected: "看起來卡住了", ev_hang_action: "卡住:已處理",
|
|
96
|
-
ev_instance_paused: "已暫停", ev_instance_resumed: "已繼續", ev_interactive_prompt: "停在提示上等待", ev_interactive_prompt_assist: "提示:已請求協助",
|
|
96
|
+
ev_instance_paused: "已暫停", ev_instance_resumed: "已繼續", ev_interactive_prompt: "停在提示上等待", ev_interactive_prompt_assist: "提示:已請求協助", ev_remote_card: "提出問題(已貼到聊天)", ev_remote_card_answer: "問題已從聊天回答",
|
|
97
97
|
ev_dialog_parked: "有對話框在等待", ev_model_failover: "改用另一個模型", ev_model_recovered: "模型恢復",
|
|
98
98
|
ev_mcp_died: "工具伺服器停止", ev_mcp_recovered: "工具伺服器恢復", ev_mcp_auto_restart: "工具伺服器已重新啟動",
|
|
99
99
|
ev_message_failed: "有訊息沒有送達", ev_pty_error: "終端機錯誤", ev_pty_recovered: "終端機恢復",
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -196,7 +196,7 @@ function NeedsItem({ item, nowMs, owner }) {
|
|
|
196
196
|
<div class="n-title"><strong>${reasonText(item)}</strong><span class="n-age">${age(item.since, nowMs)}</span></div>
|
|
197
197
|
${detailOf(item) ? html`<p class="n-detail">${detailOf(item)}</p>` : null}
|
|
198
198
|
<div class="n-actions">
|
|
199
|
-
${item.type === "prompt" && Array.isArray(item.actions) ? item.actions.map((a) => html`<button key=${a.id} type="button" class="btn btn-sm"
|
|
199
|
+
${item.type === "prompt" && Array.isArray(item.actions) ? item.actions.filter((a) => !a.input).map((a) => html`<button key=${a.id} type="button" class="btn btn-sm"
|
|
200
200
|
disabled=${promptBusy} onClick=${() => answer(a.id)}>${(p ? p.busy : fallback === "busy") ? tn("answering") : a.label}</button>`) : null}
|
|
201
201
|
${item.type === "delivery" ? html`<button type="button" class="btn btn-sm" disabled=${!!ack} onClick=${acknowledge}>
|
|
202
202
|
<${Icon} name="check" size=${14} />${ack === "busy" ? tn("acknowledging") : tn("acknowledge")}</button>` : null}
|
|
@@ -27,7 +27,7 @@ import "./settings-strings.js";
|
|
|
27
27
|
import { api, attach, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
28
28
|
import { dismissOperation, operationActive, restartFleet, startOperation, takeLeftover } from "./settings-apply.js";
|
|
29
29
|
import {
|
|
30
|
-
ACCESS_MODES, AGENT_MODES, DASHBOARD_FEATURES, DEFAULT_SCHEMA, ownChoiceImpact, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
|
|
30
|
+
ACCESS_MODES, AGENT_MODES, DASHBOARD_FEATURES, SEPARATE_FEATURE_KEYS, DEFAULT_SCHEMA, ownChoiceImpact, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
|
|
31
31
|
changedFields, channelId, channelsOf, chLabel, connectionState, dashboardDefault, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
|
|
32
32
|
hasOwn, impactOf, localeOptions, defaultsImpact, requestImpact, nonNegative, positive, replyGuardSupported, sameValue, shortName, STATUS_ORDER, toYaml,
|
|
33
33
|
toolSetDefault, visibilityDefault, worstImpact,
|
|
@@ -1024,16 +1024,16 @@ function General({ ctx, part = "all" }) {
|
|
|
1024
1024
|
tips: noticeMode(f.tips), reply_triggers: f.replyTriggers, reply_completion_guard: f.reply, log_level: f.log_level,
|
|
1025
1025
|
// The dashboard features every agent may use unless its own setting says otherwise. All of them go, because the write
|
|
1026
1026
|
// to the fleet's defaults merges top-level keys only (`dashboard` is replaced whole): the untouched ones as saved.
|
|
1027
|
-
dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => k
|
|
1028
|
-
|
|
1027
|
+
dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => !Object.hasOwn(SEPARATE_FEATURE_KEYS, k)).map((k) => [k, f[`dash_${k}`]])),
|
|
1028
|
+
...Object.fromEntries(Object.entries(SEPARATE_FEATURE_KEYS).map(([k, key]) => [key, f[`dash_${k}`]])),
|
|
1029
1029
|
hang_detector: { enabled: f.hangOn, timeout_minutes: num(f.hang) },
|
|
1030
1030
|
startup: { ...(f.concurrency.trim() === "" ? {} : { concurrency: num(f.concurrency) }), stagger_delay_ms: num(f.stagger) },
|
|
1031
1031
|
}, {
|
|
1032
1032
|
backend: d.backend || "claude-code", model: d.model || undefined, locale: d.locale || null, auto_pause_after: d.auto_pause_after ?? 0,
|
|
1033
1033
|
agent_mode: d.agent_mode || "mcp", tool_set: TOOL_SETS.includes(d.tool_set) ? d.tool_set : null, tool_progress: d.tool_progress ?? "off",
|
|
1034
1034
|
tips: d.tips ?? true, cross_instance_visibility: visibilityDefault(d), reply_triggers: Boolean(d.reply_triggers ?? schema.reply_triggers_default), reply_completion_guard: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
|
|
1035
|
-
dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => k
|
|
1036
|
-
|
|
1035
|
+
dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => !Object.hasOwn(SEPARATE_FEATURE_KEYS, k)).map((k) => [k, dashboardDefault(d, k)])),
|
|
1036
|
+
...Object.fromEntries(Object.entries(SEPARATE_FEATURE_KEYS).map(([k, key]) => [key, dashboardDefault(d, k)])),
|
|
1037
1037
|
hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES },
|
|
1038
1038
|
startup: { ...(startup.concurrency == null ? {} : { concurrency: startup.concurrency }), stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
|
|
1039
1039
|
});
|
|
@@ -40,26 +40,32 @@ export function changedFields(next, baseline) {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* The dashboard features an agent may use (dashboard-permissions.ts): each "inherit" (the fleet default: `defaults.dashboard`,
|
|
43
|
-
* else allowed — off for OFF_UNLESS_ON, `office`), "on" or "off". The patch names only the features that changed: null
|
|
43
|
+
* else allowed — off for OFF_UNLESS_ON, `office` and `office_packs`), "on" or "off". The patch names only the features that changed: null
|
|
44
44
|
* follows the default again.
|
|
45
45
|
*/
|
|
46
|
-
export const DASHBOARD_FEATURES = ["appearance", "task_board", "web_settings", "office"];
|
|
47
|
-
/** #2578: the features a person switches on — unset is off (dashboard-permissions.ts OFF_UNLESS_ON). */
|
|
48
|
-
export const OFF_UNLESS_ON = ["office"];
|
|
46
|
+
export const DASHBOARD_FEATURES = ["appearance", "task_board", "web_settings", "office", "office_packs"];
|
|
47
|
+
/** #2578, #2692: the features a person switches on — unset is off (dashboard-permissions.ts OFF_UNLESS_ON). */
|
|
48
|
+
export const OFF_UNLESS_ON = ["office", "office_packs"];
|
|
49
|
+
/** #2601, #2692: the features kept beside `dashboard` under their own key (dashboard-permissions.ts SEPARATE_FEATURE_KEYS). */
|
|
50
|
+
export const SEPARATE_FEATURE_KEYS = { office: "office_access", office_packs: "office_pack_access" };
|
|
49
51
|
export const DASHBOARD_CHOICES = ["inherit", "on", "off"];
|
|
50
52
|
const dashboardOf = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : {});
|
|
51
53
|
/** What a feature is for an agent with no setting of its own: the default's, else allowed (off for OFF_UNLESS_ON). */
|
|
52
54
|
export const dashboardDefault = (defaults, feature) => {
|
|
53
|
-
const
|
|
55
|
+
const key = Object.hasOwn(SEPARATE_FEATURE_KEYS, feature) ? SEPARATE_FEATURE_KEYS[feature] : null;
|
|
56
|
+
const v = key && defaults && Object.hasOwn(defaults, key) ? defaults[key] : dashboardOf(defaults && defaults.dashboard)[feature];
|
|
54
57
|
return OFF_UNLESS_ON.includes(feature) ? v === true : v !== false;
|
|
55
58
|
};
|
|
56
59
|
export function dashboardForm(inst) {
|
|
57
|
-
const own = { ...dashboardOf(inst.dashboard)
|
|
60
|
+
const own = { ...dashboardOf(inst.dashboard) };
|
|
61
|
+
for (const [feature, key] of Object.entries(SEPARATE_FEATURE_KEYS)) if (Object.hasOwn(inst, key)) own[feature] = inst[key];
|
|
58
62
|
return Object.fromEntries(DASHBOARD_FEATURES.map((f) => [f, own[f] === true ? "on" : own[f] === false ? "off" : "inherit"]));
|
|
59
63
|
}
|
|
60
64
|
export function dashboardPatchValue(choices) {
|
|
61
|
-
return Object.fromEntries(DASHBOARD_FEATURES.filter((f) => f
|
|
65
|
+
return Object.fromEntries(DASHBOARD_FEATURES.filter((f) => !Object.hasOwn(SEPARATE_FEATURE_KEYS, f)).map((f) => [f, choices[f] === "on" ? true : choices[f] === "off" ? false : null]));
|
|
62
66
|
}
|
|
67
|
+
/** A separately kept feature's own key in a patch (office_access, office_pack_access): true, false or null (inherit). */
|
|
68
|
+
export const separateChoice = (choice) => (choice === "on" ? true : choice === "off" ? false : null);
|
|
63
69
|
|
|
64
70
|
/** #1302: cross-instance notices. Unset, or a value the fleet does not know, means full. */
|
|
65
71
|
export const visibilityDefault = (defaults) => (VISIBILITY_MODES.includes(defaults && defaults.cross_instance_visibility) ? defaults.cross_instance_visibility : "full");
|
|
@@ -181,7 +187,8 @@ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmoji
|
|
|
181
187
|
display_name: form.inherit.display_name ? null : form.display_name.trim(),
|
|
182
188
|
status_emojis: form.status_emojis,
|
|
183
189
|
dashboard: dashboardPatchValue(form.dashboard),
|
|
184
|
-
office_access: form.dashboard.office
|
|
190
|
+
office_access: separateChoice(form.dashboard.office),
|
|
191
|
+
office_pack_access: separateChoice(form.dashboard.office_packs),
|
|
185
192
|
};
|
|
186
193
|
return changedFields(next, {
|
|
187
194
|
backend: inst.backend || defaults.backend || "claude-code",
|
|
@@ -205,7 +212,8 @@ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmoji
|
|
|
205
212
|
display_name: hasOwn(inst, "display_name") ? inst.display_name : null,
|
|
206
213
|
status_emojis: statusEmojisBaseline,
|
|
207
214
|
dashboard: dashboardPatchValue(dashboardForm(inst)),
|
|
208
|
-
office_access: dashboardForm(inst).office
|
|
215
|
+
office_access: separateChoice(dashboardForm(inst).office),
|
|
216
|
+
office_pack_access: separateChoice(dashboardForm(inst).office_packs),
|
|
209
217
|
});
|
|
210
218
|
}
|
|
211
219
|
|
|
@@ -113,6 +113,7 @@ register("settings", {
|
|
|
113
113
|
dashboard_task_board: "The task board",
|
|
114
114
|
dashboard_web_settings: "Your web appearance settings",
|
|
115
115
|
dashboard_office: "Office: walk, speak and act in the Office view",
|
|
116
|
+
dashboard_office_packs: "Office skin packs: check, upload, replace and restore them (everyone sees them)",
|
|
116
117
|
dashboardInherit: "Fleet default ({0})",
|
|
117
118
|
dashboardOn: "allowed",
|
|
118
119
|
dashboardOff: "off",
|
|
@@ -762,6 +763,7 @@ register("settings", {
|
|
|
762
763
|
dashboard_task_board: "任務看板",
|
|
763
764
|
dashboard_web_settings: "你的網頁外觀設定",
|
|
764
765
|
dashboard_office: "辦公室:在辦公室畫面走動、說話、做動作",
|
|
766
|
+
dashboard_office_packs: "辦公室外觀包:檢查、上傳、取代與還原(全 fleet 都看得到)",
|
|
765
767
|
dashboardInherit: "跟 fleet 預設({0})",
|
|
766
768
|
dashboardOn: "允許",
|
|
767
769
|
dashboardOff: "關閉",
|
|
@@ -105,7 +105,7 @@ register("app", {
|
|
|
105
105
|
confirmTitle: "Please confirm", confirmOk: "Continue", cancel: "Cancel",
|
|
106
106
|
sidePanelLeave: "Leave {0} without saving? Your changes there will be lost.", sidePanelDiscard: "Discard changes",
|
|
107
107
|
// The server's own wording (src/locale.ts needs.reason.*), so the web says what Discord says.
|
|
108
|
-
needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_backend_signin: "Backend sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
|
|
108
|
+
needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_question: "Asking you a question", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_backend_signin: "Backend sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
|
|
109
109
|
},
|
|
110
110
|
"zh-TW": {
|
|
111
111
|
brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance", newAgent: "新增 Agent",
|
|
@@ -145,6 +145,6 @@ register("app", {
|
|
|
145
145
|
search: "搜尋", searchTitle: "搜尋聊天、agent 之間的訊息與任務({0})",
|
|
146
146
|
confirmTitle: "請確認", confirmOk: "繼續", cancel: "取消",
|
|
147
147
|
sidePanelLeave: "不儲存就離開「{0}」?你在那裡的修改會遺失。", sidePanelDiscard: "捨棄修改",
|
|
148
|
-
needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_backend_signin: "後端需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
|
|
148
|
+
needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_question: "正在問你問題", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_backend_signin: "後端需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
|
|
149
149
|
},
|
|
150
150
|
});
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
// offices — the stored offices changed ({ rev }; SSE only: the Office view reads /ui/offices again).
|
|
38
38
|
// office_skins — a skin pack was uploaded or deleted ({ rev }; SSE only: the Office view reads /ui/office-skins again).
|
|
39
39
|
// office_acts — the agents' live acts in the Office view changed ({ acts }, #2578; a poll page reads /ui/office-acts).
|
|
40
|
-
const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "reactions", "steps_tick", "clock", "traffic", "delegation", "backend_login", "tasks", "offices", "office_skins", "office_acts"];
|
|
40
|
+
const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "reactions", "steps_tick", "clock", "traffic", "delegation", "backend_login", "tasks", "offices", "office_skins", "office_acts", "office_pack_changes"];
|
|
41
41
|
const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
|
|
42
42
|
const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
|
|
43
43
|
const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
|