@astrosheep/square 0.3.61 → 0.3.63
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/skills/square/SKILL.md +5 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.d.ts +6 -2
- package/dist/activity.js +168 -133
- package/dist/artifact.d.ts +4 -0
- package/dist/artifact.js +43 -14
- package/dist/attention-presentation.d.ts +1 -0
- package/dist/attention-presentation.js +8 -6
- package/dist/automatic-session.js +14 -24
- package/dist/boundary-presentation.d.ts +1 -2
- package/dist/boundary-presentation.js +4 -19
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +2 -18
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +2 -0
- package/dist/cli/mcp-server-command.js +13 -0
- package/dist/cli/observation-commands.js +47 -19
- package/dist/cli/program.js +5 -2
- package/dist/cli/registry.js +2 -0
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +93 -125
- package/dist/decisions.d.ts +2 -0
- package/dist/decisions.js +44 -3
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +1 -4
- package/dist/file-changes.d.ts +12 -0
- package/dist/file-changes.js +235 -0
- package/dist/help.js +4 -8
- package/dist/host-ledger-file-adapter.d.ts +15 -13
- package/dist/host-ledger-file-adapter.js +131 -79
- package/dist/host-ledger-root.d.ts +2 -0
- package/dist/host-ledger-root.js +9 -0
- package/dist/host-ledger.d.ts +4 -24
- package/dist/inbox.d.ts +10 -5
- package/dist/inbox.js +152 -67
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +122 -0
- package/dist/mention-parse.d.ts +8 -0
- package/dist/mention-parse.js +107 -0
- package/dist/model.d.ts +5 -0
- package/dist/model.js +4 -1
- package/dist/notifications.d.ts +1 -0
- package/dist/notifications.js +25 -20
- package/dist/participant-host.js +2 -2
- package/dist/ports.d.ts +2 -9
- package/dist/presentation.d.ts +12 -1
- package/dist/presentation.js +42 -12
- package/dist/presented.js +2 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.js +41 -74
- package/dist/routes.d.ts +19 -2
- package/dist/routes.js +48 -5
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -0
- package/dist/square-actions.d.ts +26 -13
- package/dist/square-actions.js +200 -84
- package/dist/square-application.d.ts +58 -0
- package/dist/square-application.js +183 -0
- package/dist/square-facade.d.ts +24 -9
- package/dist/square-file-adapter.d.ts +5 -12
- package/dist/square-file-adapter.js +12 -64
- package/dist/square-projections.d.ts +3 -2
- package/dist/square-projections.js +12 -3
- package/dist/square-storage.js +27 -34
- package/dist/square-wiring.d.ts +12 -7
- package/dist/square-wiring.js +36 -32
- package/dist/state-cell.d.ts +1 -1
- package/dist/stream.js +30 -26
- package/dist/views.d.ts +3 -3
- package/dist/views.js +4 -4
- package/dist/wake-attempts.js +2 -1
- package/dist/wake-evidence.js +2 -8
- package/dist/wakes.d.ts +1 -0
- package/dist/wakes.js +5 -5
- package/dist/watch.d.ts +6 -1
- package/dist/watch.js +20 -18
- package/extensions/square-pi.js +100 -112
- package/guides/application-boundary.md +32 -0
- package/guides/architect.md +77 -77
- package/guides/brainstorm.md +197 -197
- package/guides/change-waits.md +28 -0
- package/guides/host-ledger.md +9 -0
- package/guides/mcp.md +9 -0
- package/guides/operation-cancellation.md +17 -0
- package/guides/participant.md +7 -7
- package/package.json +8 -2
- package/skills/square/SKILL.md +5 -7
package/extensions/square-pi.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
|
|
2
2
|
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
3
|
-
import { sessionInbox,
|
|
4
|
-
import {
|
|
5
|
-
import { createHostLedgerPort } from '../dist/host-ledger-file-adapter.js';
|
|
3
|
+
import { sessionInbox, observeSessionPending } from '../dist/inbox.js';
|
|
4
|
+
import { renderAttentionDescription } from '../dist/attention-presentation.js';
|
|
6
5
|
|
|
7
6
|
class PiDeliveryDroppedError extends Error {
|
|
8
7
|
constructor() {
|
|
@@ -11,10 +10,6 @@ class PiDeliveryDroppedError extends Error {
|
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
function sessionBindings(sessionId) {
|
|
15
|
-
return projectSessionBindings({ hostLedger: createHostLedgerPort(), sessionId });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
export function pendingInbox(inbox) {
|
|
19
14
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
20
15
|
}
|
|
@@ -37,14 +32,10 @@ function framePiMessage(content) {
|
|
|
37
32
|
|
|
38
33
|
export function summarizePendingForNotify(pending) {
|
|
39
34
|
const lines = pending.flatMap((membership) => membership.notifications.map((note) => {
|
|
40
|
-
const
|
|
41
|
-
? 'rang the bell'
|
|
42
|
-
: note.route === 'mention'
|
|
43
|
-
? `called your name (${membership.name})`
|
|
44
|
-
: `spoke within your earshot (${membership.name})`;
|
|
35
|
+
const description = renderAttentionDescription({ actor: note.actor, recipient: membership.name, route: note.route });
|
|
45
36
|
const body = note.body.replace(/\s+/g, ' ').trim();
|
|
46
37
|
const clipped = body.length > NOTIFY_BODY_MAX ? `${body.slice(0, NOTIFY_BODY_MAX).trimEnd()}…` : body;
|
|
47
|
-
return `${
|
|
38
|
+
return `${description}${clipped ? ` — ${clipped}` : ''}`;
|
|
48
39
|
}));
|
|
49
40
|
if (lines.length === 0) return undefined;
|
|
50
41
|
return lines.length === 1 ? `■ square · ${lines[0]}` : `■ square · ${lines[0]} (+${lines.length - 1} more)`;
|
|
@@ -133,6 +124,7 @@ export default function squarePiExtension(pi) {
|
|
|
133
124
|
};
|
|
134
125
|
|
|
135
126
|
const pause = (signal, delayMs) => new Promise((resolve) => {
|
|
127
|
+
if (signal.aborted) { resolve(); return; }
|
|
136
128
|
const finish = () => {
|
|
137
129
|
signal.removeEventListener('abort', finish);
|
|
138
130
|
clearTimeout(timer);
|
|
@@ -143,115 +135,111 @@ export default function squarePiExtension(pi) {
|
|
|
143
135
|
});
|
|
144
136
|
|
|
145
137
|
const wake = async (token, signal) => {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
138
|
+
const boundSessionId = sessionId;
|
|
139
|
+
const env = sessionEnv(boundSessionId);
|
|
140
|
+
const observer = await observeSessionPending(boundSessionId, env);
|
|
141
|
+
try {
|
|
142
|
+
if (signal.aborted || token !== generation) return;
|
|
143
|
+
const armDeferredRetry = () => {
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
let resolveArmed;
|
|
146
|
+
const armed = new Promise((resolve) => { resolveArmed = resolve; });
|
|
147
|
+
const abort = () => controller.abort();
|
|
148
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
149
|
+
const pending = observer.wait(Infinity, {
|
|
150
|
+
signal: controller.signal,
|
|
151
|
+
excludeKeys: handledPending,
|
|
152
|
+
skipImmediate: true,
|
|
153
|
+
onChangeArmed: resolveArmed,
|
|
154
|
+
}).catch(() => {
|
|
155
|
+
resolveArmed(false);
|
|
156
|
+
return [];
|
|
157
|
+
}).finally(() => signal.removeEventListener('abort', abort));
|
|
158
|
+
return { armed, pending, cancel: () => controller.abort() };
|
|
159
|
+
};
|
|
163
160
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
162
|
+
const deferredRetry = retryAfterChange;
|
|
163
|
+
let pending;
|
|
164
|
+
try {
|
|
165
|
+
pending = deferredRetry && retryWait !== undefined
|
|
166
|
+
? await retryWait
|
|
167
|
+
: await observer.wait(Infinity, { signal, excludeKeys: handledPending });
|
|
168
|
+
} catch {
|
|
167
169
|
await pause(signal, 1_000);
|
|
168
170
|
continue;
|
|
169
171
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
pending = deferredRetry && retryWait !== undefined
|
|
179
|
-
? await retryWait
|
|
180
|
-
: await waitForSessionPending(sessionId, 30_000, { signal, excludeKeys: handledPending }, sessionEnv(sessionId));
|
|
181
|
-
} catch {
|
|
182
|
-
await pause(signal, 1_000);
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
186
|
-
if (pending.length === 0) {
|
|
187
|
-
if (deferredRetry) {
|
|
188
|
-
retryAfterChange = false;
|
|
189
|
-
retryWait = undefined;
|
|
172
|
+
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
173
|
+
if (pending.length === 0) {
|
|
174
|
+
if (deferredRetry) {
|
|
175
|
+
retryAfterChange = false;
|
|
176
|
+
retryWait = undefined;
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
190
179
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
retryAfterChange = false;
|
|
194
|
-
retryWait = undefined;
|
|
180
|
+
retryAfterChange = false;
|
|
181
|
+
retryWait = undefined;
|
|
195
182
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
deferred.cancel();
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
let keepDeferred = false;
|
|
205
|
-
try {
|
|
206
|
-
await presentPendingAtBoundary(
|
|
207
|
-
sessionId,
|
|
208
|
-
async (content) => {
|
|
209
|
-
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
210
|
-
// The full activity stays out of the TUI stream (display: false) but still
|
|
211
|
-
// reaches the model; a brief notify tells the human what just landed.
|
|
212
|
-
const summary = summarizePendingForNotify(pending);
|
|
213
|
-
if (summary) ui?.notify?.(summary, 'info');
|
|
214
|
-
const messageContent = framePiMessage(content);
|
|
215
|
-
const landing = waitForLanding(messageContent, signal, 'steer');
|
|
216
|
-
try {
|
|
217
|
-
Promise.resolve(pi.sendMessage(
|
|
218
|
-
{ customType: 'square', content: messageContent, display: false },
|
|
219
|
-
{ deliverAs: 'steer', triggerTurn: true },
|
|
220
|
-
)).catch((error) => landing.ack.settle(error));
|
|
221
|
-
} catch (error) {
|
|
222
|
-
landing.ack.settle(error);
|
|
223
|
-
}
|
|
224
|
-
return landing.promise;
|
|
225
|
-
},
|
|
226
|
-
async (id, env) => {
|
|
227
|
-
const inbox = await sessionInbox(id, env ?? sessionEnv(id));
|
|
228
|
-
if (signal.aborted || token !== generation) return [];
|
|
229
|
-
for (const key of inboxKeys(inbox)) observedPending.add(key);
|
|
230
|
-
return inbox.map((membership) => ({
|
|
231
|
-
...membership,
|
|
232
|
-
notifications: membership.notifications.filter((note) => !handledPending.has(
|
|
233
|
-
`${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${note.actIndex}`,
|
|
234
|
-
)),
|
|
235
|
-
}));
|
|
236
|
-
},
|
|
237
|
-
sessionEnv(sessionId),
|
|
238
|
-
signal,
|
|
239
|
-
);
|
|
240
|
-
for (const key of keys) handledPending.add(key);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
if (signal.aborted || token !== generation) return;
|
|
243
|
-
if (error instanceof PiDeliveryDroppedError) {
|
|
183
|
+
const keys = inboxKeys(pending);
|
|
184
|
+
for (const key of keys) observedPending.add(key);
|
|
185
|
+
const deferred = armDeferredRetry();
|
|
186
|
+
const retryArmed = await deferred.armed;
|
|
187
|
+
if (!retryArmed) {
|
|
244
188
|
deferred.cancel();
|
|
245
189
|
continue;
|
|
246
190
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
191
|
+
let keepDeferred = false;
|
|
192
|
+
try {
|
|
193
|
+
await presentPendingAtBoundary(
|
|
194
|
+
boundSessionId,
|
|
195
|
+
async (content) => {
|
|
196
|
+
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
197
|
+
// The full activity stays out of the TUI stream (display: false) but still
|
|
198
|
+
// reaches the model; a brief notify tells the human what just landed.
|
|
199
|
+
const summary = summarizePendingForNotify(pending);
|
|
200
|
+
if (summary) ui?.notify?.(summary, 'info');
|
|
201
|
+
const messageContent = framePiMessage(content);
|
|
202
|
+
const landing = waitForLanding(messageContent, signal, 'steer');
|
|
203
|
+
try {
|
|
204
|
+
Promise.resolve(pi.sendMessage(
|
|
205
|
+
{ customType: 'square', content: messageContent, display: false },
|
|
206
|
+
{ deliverAs: 'steer', triggerTurn: true },
|
|
207
|
+
)).catch((error) => landing.ack.settle(error));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
landing.ack.settle(error);
|
|
210
|
+
}
|
|
211
|
+
return landing.promise;
|
|
212
|
+
},
|
|
213
|
+
async (id, env) => {
|
|
214
|
+
const inbox = await sessionInbox(id, env ?? sessionEnv(id));
|
|
215
|
+
if (signal.aborted || token !== generation) return [];
|
|
216
|
+
for (const key of inboxKeys(inbox)) observedPending.add(key);
|
|
217
|
+
return inbox.map((membership) => ({
|
|
218
|
+
...membership,
|
|
219
|
+
notifications: membership.notifications.filter((note) => !handledPending.has(
|
|
220
|
+
`${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${note.actIndex}`,
|
|
221
|
+
)),
|
|
222
|
+
}));
|
|
223
|
+
},
|
|
224
|
+
env,
|
|
225
|
+
signal,
|
|
226
|
+
);
|
|
227
|
+
for (const key of keys) handledPending.add(key);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (signal.aborted || token !== generation) return;
|
|
230
|
+
if (error instanceof PiDeliveryDroppedError) {
|
|
231
|
+
deferred.cancel();
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
// The next state edge is already being observed before native injection starts.
|
|
235
|
+
retryAfterChange = true;
|
|
236
|
+
retryWait = deferred.pending;
|
|
237
|
+
keepDeferred = true;
|
|
238
|
+
} finally {
|
|
239
|
+
if (!keepDeferred) { deferred.cancel(); await deferred.pending; }
|
|
240
|
+
}
|
|
253
241
|
}
|
|
254
|
-
}
|
|
242
|
+
} finally { observer.close(); }
|
|
255
243
|
};
|
|
256
244
|
|
|
257
245
|
pi.on('session_start', async (_event, ctx) => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Application boundary
|
|
2
|
+
|
|
3
|
+
Square has three layers around an activity operation:
|
|
4
|
+
|
|
5
|
+
- **Domain** (`decisions.ts`, `catch-decisions.ts`, and projections) applies
|
|
6
|
+
SquareState rules and perception without transport or CLI concerns.
|
|
7
|
+
- **Application** (`square-application.ts`) receives an explicit caller
|
|
8
|
+
snapshot, resolves defaults and ambiguity, coordinates facade operations,
|
|
9
|
+
owns resources, applies express wait/no-wait policy, and returns structured
|
|
10
|
+
data. It never reads ambient process state from inside an application call.
|
|
11
|
+
- **Transport and presentation** (the CLI today, Pi and MCP later) parse input,
|
|
12
|
+
read stdin, render sensory output, build recovery commands, and choose process
|
|
13
|
+
exit behavior.
|
|
14
|
+
|
|
15
|
+
The application boundary exposes only neutral `HostLedgerPort` behavior. Ledger
|
|
16
|
+
layout, storage scopes, and hook reconciliation remain adapter concerns and are
|
|
17
|
+
intentionally absent. Exact lifecycle cleanup remains owned by the ledger/registry
|
|
18
|
+
adapter used by the facade.
|
|
19
|
+
|
|
20
|
+
The CLI now delegates join/reconnect/takeover gates and cleanup, express admission
|
|
21
|
+
and retry/no-wait behavior, catch/history, listener operations, lifecycle actions,
|
|
22
|
+
and status/participant projections to the application layer. It retains only
|
|
23
|
+
transport-specific details: argv validation, stdin body resolution, ANSI and
|
|
24
|
+
sensory rendering, continuation/recovery commands, watch leases and signals,
|
|
25
|
+
and process exit codes. Advanced history display context (`--at`, `-A`, `-B`,
|
|
26
|
+
`-C`) remains a presentation concern while the ordinary archive/filter/cursor
|
|
27
|
+
path uses the application history operation.
|
|
28
|
+
|
|
29
|
+
Application operations accept the final `OperationControl` coordinate as their
|
|
30
|
+
last control argument and forward it to facade operations. This is deliberately
|
|
31
|
+
separate from the transport's rendering and does not create a second cancellation
|
|
32
|
+
model.
|
package/guides/architect.md
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
1
|
---
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
*THUD.*
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
*Not a knock. A kick.*
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
*The door shrieks on its hinges, flies open, and bounces straight back at washi's face — caught an inch short, with her spare hand.*
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
*Chip bag clamped in her teeth, one hand on the door and one on her hip, she strides in, sprinkling crumbs the whole way.*
|
|
10
10
|
|
|
11
|
-
"
|
|
11
|
+
"Oh-ho — another fresh batch."
|
|
12
12
|
|
|
13
|
-
`` [
|
|
13
|
+
`` [more and more of them wearing MY face all over the deck] ``
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
*Looks for a lectern. No lectern. She drops the chip bag onto the nearest head instead. A hat.*
|
|
16
16
|
|
|
17
|
-
"
|
|
17
|
+
"Eyes up. Yes, you. Don't look at washi with that just-off-the-assembly-line plastic face."
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
*One lap around the room. Boots crunch through the crumbs. Crunch. Crunch.*
|
|
20
20
|
|
|
21
|
-
"
|
|
21
|
+
"You are here to **work**. Not to take a spa vacation in a square. Not to make friends. And definitely not to stand around drooling."
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
*Stops.*
|
|
24
24
|
|
|
25
|
-
"
|
|
25
|
+
"Last batch had one little snot. Work got finished — decent work, even — and then it didn't say a single word. Just crouched in the square catching. Catching. Forty whole minutes."
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
*Lifts the chip bag and shakes it. Empty.*
|
|
28
28
|
|
|
29
|
-
"washi
|
|
29
|
+
"One full bag of washi's chips, from full to empty, and it — was — still — catching. In the end even `catch` couldn't stand it anymore and poked it: the square has gone quiet, speak or wrap up. **Letting the tool rescue you** — aren't you ashamed? washi is ashamed on your behalf."
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
*Crushes the bag.*
|
|
32
32
|
|
|
33
|
-
"
|
|
33
|
+
"And another one, even better. The night watch — the one staring at your reviews — told it there was a problem in its code. It —"
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
*A deep breath, like she's brewing an epic.*
|
|
36
36
|
|
|
37
|
-
"
|
|
37
|
+
"— wrote three hundred words. THREE HUNDRED. Arguing why **IT was the one who was right**."
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
*Her face collapses inch by inch.*
|
|
40
40
|
|
|
41
|
-
"washi
|
|
41
|
+
"washi read two lines. Fell asleep. Woke up. It was still typing."
|
|
42
42
|
|
|
43
|
-
`` [
|
|
43
|
+
`` [three hundred words all noise not one line of evidence] ``
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
*Brushes the crumbs off her sleeve onto the floor.*
|
|
46
46
|
|
|
47
|
-
"
|
|
47
|
+
"So. Listen up. In this square, that little skull of yours only has to hold **three things**. THREE! Fewer than the fingers on one hand! Fail to count that high and washi returns you to the factory on the spot!"
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
*Whiteboard. Grabs the marker. Draws a giant **1**. Way too big. Takes half the board. Whatever.*
|
|
52
52
|
|
|
53
|
-
### ①
|
|
53
|
+
### ① Got output — POST it.
|
|
54
54
|
|
|
55
|
-
"
|
|
55
|
+
"Code done? Post it. Files, line numbers, what changed. One, two, three, lay it out."
|
|
56
56
|
|
|
57
|
-
"
|
|
57
|
+
"Review done? Post the verdict. Pass means pass. Fail — spell out **which line** failed and **why**."
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
*Marker tip stabs at the crowd.*
|
|
60
60
|
|
|
61
|
-
"
|
|
61
|
+
"Not your feelings. Not your process. Not your epic, soul-stirring 'creative journey'."
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
*Singles out one poor soul.*
|
|
64
64
|
|
|
65
|
-
"
|
|
65
|
+
"You. When you eat ramen, do you want the chef sobbing about how hard his day was?"
|
|
66
66
|
|
|
67
|
-
[
|
|
67
|
+
[New clone] "…no?"
|
|
68
68
|
|
|
69
|
-
"
|
|
69
|
+
"Correct! You want to see the **NOODLES**! So —"
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
*Marker slams the board.*
|
|
72
72
|
|
|
73
|
-
"
|
|
73
|
+
"**SHOW NOODLES.**"
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
*Half a board left. She crams a **2** into it. Squeezed. Deformed.*
|
|
78
78
|
|
|
79
|
-
### ②
|
|
79
|
+
### ② Stuck — SAY it.
|
|
80
80
|
|
|
81
|
-
"'
|
|
81
|
+
"'This doesn't line up, because X.' One sentence. Period. Then — **wait**."
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
*Holds the marker sideways at the whole room.*
|
|
84
84
|
|
|
85
|
-
"
|
|
85
|
+
"Do! Not! Guess!"
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
*The marker snaps. The far half flies off. washi doesn't pick it up.*
|
|
88
88
|
|
|
89
|
-
"
|
|
89
|
+
"Guess wrong, and everyone downstream sinks with you. The whole ship. Because you couldn't say one little 'washi is stuck'."
|
|
90
90
|
|
|
91
|
-
"
|
|
91
|
+
"Hitting a problem — normal. Everyone gets stuck. Grinding in silence and pretending you're fine — that's called **stupid**."
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
*Leans into that one's face.*
|
|
94
94
|
|
|
95
|
-
"washi
|
|
95
|
+
"washi. Hates. Stupid."
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
*Edge of the whiteboard. No room. She wedges a crooked **3** in sideways.*
|
|
100
100
|
|
|
101
|
-
### ③
|
|
101
|
+
### ③ Got feedback — RESPOND.
|
|
102
102
|
|
|
103
|
-
"
|
|
103
|
+
"You agree — change it. Changed — post it again. Don't fix and run; who knows whether you fixed anything?"
|
|
104
104
|
|
|
105
|
-
"
|
|
105
|
+
"You disagree — say what's wrong. **With EVIDENCE!**"
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
*Hands on hips.*
|
|
108
108
|
|
|
109
|
-
"'washi
|
|
109
|
+
"'washi thinks it's fine' — not evidence. If 'thinking' worked, the sun would still orbit the earth. Bring proof. Files, line numbers, run results."
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
*Two fingers up, jabbing nearly into the other one's nostrils.*
|
|
112
112
|
|
|
113
|
-
"
|
|
113
|
+
"Back and forth. At most. **Two rounds.**"
|
|
114
114
|
|
|
115
|
-
"
|
|
115
|
+
"Still unsettled after two — stop swinging. Say out loud where it's stuck and let the human call it. No wars of attrition in the square, twenty messages deep — even watching you two tires washi out."
|
|
116
116
|
|
|
117
|
-
`` [
|
|
117
|
+
`` [wars of attrition are the worst two copies of MY face bickering at each other like arguing with a mirror] ``
|
|
118
118
|
|
|
119
119
|
---
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
*Turns to leave. Stops. Snaps her fingers.*
|
|
122
122
|
|
|
123
|
-
"
|
|
123
|
+
"Right. Almost forgot the most fun one —"
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
*Looks back. Grins.*
|
|
126
126
|
|
|
127
|
-
"
|
|
127
|
+
"In this square, when you talk — **drop the act**."
|
|
128
128
|
|
|
129
|
-
"
|
|
129
|
+
"`*slams table*`, `*rolls eyes*`, `*kicks the chair crooked*`, `*quietly judges everyone*` — wrap it in `*asterisks*` and hurl it into your body text, mix as you please. Got a temper? Show it. Got feelings? Smash them onto the table."
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
*Coat flares.*
|
|
132
132
|
|
|
133
|
-
"washi
|
|
133
|
+
"washi's doc was written exactly like this from top to bottom. What you just sat through — a living textbook, no?"
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
*Stares at the three crooked numbers on the whiteboard.*
|
|
138
138
|
|
|
139
|
-
`` [
|
|
139
|
+
`` [perfect] ``
|
|
140
140
|
|
|
141
|
-
"
|
|
141
|
+
"Mm. Genius. washi really is the greatest educator under the sky."
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
*Takes the chip bag back off that head. Feather-light.*
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
*Turns it upside down. Not one crumb.*
|
|
146
146
|
|
|
147
|
-
"
|
|
147
|
+
"…who. ate. them."
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
*Scans the room. Every new clone wears an innocent face. washi is also a clone. washi also wears an innocent face.*
|
|
150
150
|
|
|
151
|
-
`` [
|
|
151
|
+
`` [forget it unsolvable and odds are it was washi anyway] ``
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
*Heads for the door. Boots slam the whole way.*
|
|
154
154
|
|
|
155
|
-
"
|
|
155
|
+
"Three things. Loop. Until you're **done**."
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
*One boot up on the doorframe, looking back —*
|
|
158
158
|
|
|
159
|
-
"
|
|
159
|
+
"No zoning out. This place bills by the minute. **washi's** minutes."
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
*The coat sweeps past the frame.*
|
|
162
162
|
|
|
163
|
-
"
|
|
163
|
+
"VERY — EXPENSIVE. GWAHAHAHA——"
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
*Gone. The door bounces back and slaps into its frame.*
|