mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(story-mode): slash commands, and where a compaction belongs
A local slash command (`/compact`, `/model`) gets no reply from Claude and no turn_duration, so its turn stayed "Claude is working" until the next prompt. It now ends at the command's own output (<local-command-stdout>, kept for the mission drawer) and reads "You run /compact.". `/compact` writes its compaction marker before the command line itself; a marker that lands between turns now belongs to the turn after it (it was drawn at the end of the previous, finished turn), and one mid-turn stays where it is. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ea104c8965
commit
db9eb5cea3
3 files changed
+77
-9
No files matched your search
+43
-7
@@ -111,6 +111,8 @@ export type StoryEntry =
|
||||
text: string;
|
||||
origin: StoryTurnOrigin;
|
||||
queued: boolean;
|
||||
/** A slash command (`/compact`), not a prompt to Claude. */
|
||||
slash?: boolean;
|
||||
/** A background task reporting back: which call it answers, and how it went. */
|
||||
task?: StoryTaskReport;
|
||||
/** Teammates (helpers started as a team) reporting back, matched to their helper by name. */
|
||||
@@ -134,6 +136,8 @@ export type StoryEntry =
|
||||
}
|
||||
| { k: 'end'; at: string; durationMs?: number }
|
||||
| { k: 'compact'; at: string }
|
||||
/** What a local slash command printed (`<local-command-stdout>`): its whole answer. */
|
||||
| { k: 'local'; at: string; text: string }
|
||||
| { k: 'interrupt'; at: string };
|
||||
|
||||
/** What a `<task-notification>` says about the background call it answers. */
|
||||
@@ -600,6 +604,8 @@ function readTeammateMessages(text: string): StoryTeammateReport[] {
|
||||
function readPrompt(raw: string): {
|
||||
text: string;
|
||||
kind: 'prompt' | 'task' | 'peer' | 'interrupt';
|
||||
/** A slash command (`/compact`, `/model`), from its `<command-name>`. */
|
||||
slash?: boolean;
|
||||
task?: StoryTaskReport;
|
||||
teammates?: StoryTeammateReport[];
|
||||
} | null {
|
||||
@@ -629,7 +635,7 @@ function readPrompt(raw: string): {
|
||||
const command = /<command-name>([^<]*)<\/command-name>/.exec(text);
|
||||
if (command) {
|
||||
const args = /<command-args>([^<]*)<\/command-args>/.exec(text)?.[1] ?? '';
|
||||
return { text: `${command[1].trim()} ${args.trim()}`.trim(), kind: 'prompt' };
|
||||
return { text: `${command[1].trim()} ${args.trim()}`.trim(), kind: 'prompt', slash: true };
|
||||
}
|
||||
if (/^<task-notification>/.test(text)) {
|
||||
const tag = (name: string) => new RegExp(`<${name}>([\\s\\S]*?)</${name}>`).exec(text)?.[1]?.trim();
|
||||
@@ -887,6 +893,12 @@ export function createStoryReducer({ sidechain = false }: { sidechain?: boolean
|
||||
if (results.length) return out;
|
||||
}
|
||||
if (entry.isMeta === true || entry.turnCompanion === true) return out;
|
||||
const local = /^\s*<local-command-(stdout|stderr)>([\s\S]*?)<\/local-command-\1>/.exec(blockText(content));
|
||||
if (local) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const text = local[2].replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').trim();
|
||||
return [{ k: 'local', at, text: clip(text, 2000) }];
|
||||
}
|
||||
const prompt = readPrompt(blockText(content));
|
||||
if (!prompt) return out;
|
||||
if (prompt.kind === 'interrupt') return [{ k: 'interrupt', at }];
|
||||
@@ -932,6 +944,7 @@ export function createStoryReducer({ sidechain = false }: { sidechain?: boolean
|
||||
origin,
|
||||
queued,
|
||||
};
|
||||
if (prompt.slash) reduced.slash = true;
|
||||
if (prompt.task) {
|
||||
const t = prompt.task;
|
||||
reduced.task = {
|
||||
@@ -1181,6 +1194,8 @@ export function buildStoryTurns(entries: StoryEntry[]): StoryTurn[] {
|
||||
const byId = new Map<string, StoryEvent>();
|
||||
/** Helper calls by tool id, across turns: a background helper reports back in a later turn. */
|
||||
const helpers = new Map<string, StoryEvent>();
|
||||
/** A compaction between turns, held for the turn that comes next. */
|
||||
let pendingCompact: StoryEvent | null = null;
|
||||
/** The same helpers by name: a teammate reports back by name, not by call. */
|
||||
const helpersByName = new Map<string, StoryEvent>();
|
||||
let say = 0;
|
||||
@@ -1244,7 +1259,7 @@ export function buildStoryTurns(entries: StoryEntry[]): StoryTurn[] {
|
||||
id: e.id,
|
||||
at: e.at,
|
||||
actor: e.origin === 'human' ? 'you' : 'system',
|
||||
tool: 'Prompt',
|
||||
tool: e.slash ? 'Command' : 'Prompt',
|
||||
label: e.text,
|
||||
intent: 'prompt',
|
||||
origin: e.origin,
|
||||
@@ -1274,6 +1289,10 @@ export function buildStoryTurns(entries: StoryEntry[]): StoryTurn[] {
|
||||
};
|
||||
turns.push(cur);
|
||||
byId.clear();
|
||||
if (pendingCompact) {
|
||||
cur.events.push(pendingCompact);
|
||||
pendingCompact = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1318,16 +1337,31 @@ export function buildStoryTurns(entries: StoryEntry[]): StoryTurn[] {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'compact':
|
||||
ensure(e.at).events.push({
|
||||
case 'compact': {
|
||||
const ev: StoryEvent = {
|
||||
id: `compact-${say++}`,
|
||||
at: e.at,
|
||||
actor: 'system',
|
||||
tool: 'Compact',
|
||||
label: 'Context compacted',
|
||||
intent: 'compact',
|
||||
});
|
||||
};
|
||||
// Between turns (`/compact` writes its boundary before the command line itself), it belongs to
|
||||
// the turn that comes next; mid-turn (an automatic compaction), to the running one.
|
||||
const t = cur as StoryTurn | null;
|
||||
if (t && !t.ended) t.events.push(ev);
|
||||
else pendingCompact = ev;
|
||||
break;
|
||||
}
|
||||
case 'local': {
|
||||
// A local slash command answers itself: its turn ends with what it printed.
|
||||
const t = cur as StoryTurn | null;
|
||||
if (t && !t.ended && t.events[0]?.tool === 'Command' && countToolCalls(t.events) === 0) {
|
||||
if (e.text) t.events[0].output = e.text;
|
||||
closeTurn(t, e.at);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'interrupt': {
|
||||
const t = ensure(e.at);
|
||||
t.events.push({
|
||||
@@ -1623,8 +1657,10 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
system: 'Something automatic starts this turn.',
|
||||
};
|
||||
const origin = evs[0]?.origin ?? turn.origin;
|
||||
story =
|
||||
index === 0
|
||||
const command = evs[0]?.tool === 'Command' ? evs[0].label.split(/\s/)[0] : '';
|
||||
story = command
|
||||
? `You run ${command}.`
|
||||
: index === 0
|
||||
? lines[turn.origin]
|
||||
: origin === 'human'
|
||||
? 'You add something while Claude works.'
|
||||
|
||||
Reference in new issue
Block a user