sideby 0.9.0 → 0.9.2
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/dist/build.json +1 -1
- package/dist/src/cli/commands.js +20 -8
- package/dist/src/cli/json-schemas.js +6 -1
- package/dist/src/cli/main.js +3 -2
- package/dist/src/core/handoff-chain.d.ts +5 -2
- package/dist/src/core/handoff-chain.js +4 -2
- package/dist/src/core/handoff-run.d.ts +2 -2
- package/dist/src/core/handoff-run.js +7 -7
- package/dist/src/core/handoff-select.d.ts +5 -0
- package/dist/src/core/handoff-select.js +37 -2
- package/dist/src/core/model-choice.js +4 -2
- package/dist/src/handoff/messages.d.ts +7 -1
- package/dist/src/handoff/messages.js +7 -2
- package/dist/src/panel/i18n.d.ts +1 -0
- package/dist/src/panel/i18n.js +2 -0
- package/dist/src/panel/page-script.js +14 -3
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -0
- package/package.json +1 -1
- package/schemas/handoff-ready.json +3 -0
- package/skills/sideby/SKILL.md +1 -1
- package/skills/sideby/references/handoff.md +1 -1
package/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261015T120149-abb6bb"}
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -733,17 +733,23 @@ export async function cmdHandoff(a, io) {
|
|
|
733
733
|
if (sub === 'enable' || sub === 'disable')
|
|
734
734
|
return cmdHandoffSwitch(a, io, sub);
|
|
735
735
|
if (sub !== 'ready')
|
|
736
|
-
throw new UsageError('usage: sideby handoff status | enable [--same-family] [--order <family>=<acct,…>] [--threshold n] [--prepare-at n] [--yes] | disable [--yes] | ready [--brief <file>] | setup|teardown <family> [--yes]');
|
|
736
|
+
throw new UsageError('usage: sideby handoff status | enable [--same-family] [--order <family>=<acct,…>] [--threshold n] [--prepare-at n] [--yes] | disable [--yes] | ready [--to <account>] [--brief <file>] | setup|teardown <family> [--yes]');
|
|
737
737
|
const flag = a.flags.get('brief');
|
|
738
738
|
const brief = typeof flag === 'string' ? flag : undefined;
|
|
739
|
-
const
|
|
739
|
+
const toFlag = a.flags.get('to');
|
|
740
|
+
if (toFlag === true)
|
|
741
|
+
throw new UsageError('usage: sideby handoff ready --to <account> [--brief <file>]');
|
|
742
|
+
const rt = await createRuntime();
|
|
743
|
+
// An Account or a short command; resolve() says what to do when there is no such Account.
|
|
744
|
+
const to = toFlag === undefined ? undefined : (await rt.resolve(toFlag)).ref;
|
|
745
|
+
const decision = await readyDecision(rt, process.env, to);
|
|
740
746
|
if (decision && decision.kind !== 'pick')
|
|
741
|
-
throw new UserError(
|
|
742
|
-
|
|
743
|
-
|
|
747
|
+
throw new UserError(to
|
|
748
|
+
? `cannot hand over to ${to}: ${explain(decision)}; keep working in this account or pick another with --to`
|
|
749
|
+
: `nothing can take over now: ${explain(decision)}; keep working in this account`, { code: 'no-pick' });
|
|
744
750
|
let request;
|
|
745
751
|
try {
|
|
746
|
-
request = await requestHandoff(process.env, brief);
|
|
752
|
+
request = await requestHandoff(process.env, brief, to);
|
|
747
753
|
}
|
|
748
754
|
catch (err) {
|
|
749
755
|
throw new UserError(`cannot read the brief ${brief}: ${err.message}; check the path and retry`, {
|
|
@@ -752,10 +758,16 @@ export async function cmdHandoff(a, io) {
|
|
|
752
758
|
}
|
|
753
759
|
if (!request)
|
|
754
760
|
throw new UserError('not inside a session sideby started with Auto Handoff; set `handoff.auto` in the config and start the account with sideby (`sideby run`, a short command), or use `sideby next <family>`', { code: 'not-in-handoff-run' });
|
|
761
|
+
// The account that will really take over; `sideby next` cannot tell, it does not know the chain.
|
|
762
|
+
const next = decision?.kind === 'pick' ? decision.ref : undefined;
|
|
755
763
|
if (a.flags.has('json'))
|
|
756
|
-
json(io, {
|
|
764
|
+
json(io, {
|
|
765
|
+
requested: true,
|
|
766
|
+
...(next ? { next } : {}),
|
|
767
|
+
...(request.brief ? { brief: request.brief } : {}),
|
|
768
|
+
});
|
|
757
769
|
else
|
|
758
|
-
io.out('The next account starts when this turn ends; finish the turn now
|
|
770
|
+
io.out(`${next ? `${next} takes over` : 'The next account starts'} when this turn ends; finish the turn now.`);
|
|
759
771
|
return 0;
|
|
760
772
|
}
|
|
761
773
|
export async function cmdPlugins(a, io) {
|
|
@@ -351,7 +351,12 @@ export const OUTPUT_SCHEMAS = {
|
|
|
351
351
|
nextSteps: Type.Array(Str),
|
|
352
352
|
}, { title: `sideby handoff ${sub} --json` }),
|
|
353
353
|
])),
|
|
354
|
-
'handoff-ready': Type.Object({
|
|
354
|
+
'handoff-ready': Type.Object({
|
|
355
|
+
schemaVersion: Version,
|
|
356
|
+
requested: Type.Literal(true),
|
|
357
|
+
next: Type.Optional(Str),
|
|
358
|
+
brief: Type.Optional(Str),
|
|
359
|
+
}, { title: 'sideby handoff ready --json' }),
|
|
355
360
|
'handoff-setup': Type.Object({ schemaVersion: Version, family: Str, applied: Type.Boolean(), plan: QuotaSetupPlan }, { title: 'sideby handoff setup --json' }),
|
|
356
361
|
'handoff-teardown': Type.Object({ schemaVersion: Version, family: Str, ok: Type.Boolean(), message: Str, diff: Type.Optional(Str) }, { title: 'sideby handoff teardown --json' }),
|
|
357
362
|
'quota-teardown': Type.Object({ schemaVersion: Version, family: Str, ok: Type.Boolean(), message: Str, diff: Type.Optional(Str) }, { title: 'sideby quota teardown --json' }),
|
package/dist/src/cli/main.js
CHANGED
|
@@ -39,7 +39,8 @@ Usage:
|
|
|
39
39
|
--order claude=codex001,cc002, --threshold n, --prepare-at n
|
|
40
40
|
sideby handoff disable [--yes] turn it off, keeping the other settings
|
|
41
41
|
sideby handoff ready [--brief <f>] inside a session sideby started with Auto Handoff: hand over to
|
|
42
|
-
the next account when this turn ends (config \`handoff\`)
|
|
42
|
+
the next account when this turn ends (config \`handoff\`);
|
|
43
|
+
--to <account> names it, also one this chain already used
|
|
43
44
|
sideby handoff setup grok [--yes] add sideby's hook file so Grok can hand over at its limit
|
|
44
45
|
sideby handoff teardown grok remove it
|
|
45
46
|
sideby ui [--port n] [--no-open] open the local panel
|
|
@@ -180,7 +181,7 @@ const COMMANDS = {
|
|
|
180
181
|
model: { flags: ['json', 'yes'], valued: ['effort'] },
|
|
181
182
|
handoff: {
|
|
182
183
|
flags: ['json', 'yes', 'same-family', 'no-same-family'],
|
|
183
|
-
valued: ['brief', 'order', 'prepare-at', 'threshold', 'wait-minutes', 'countdown'],
|
|
184
|
+
valued: ['brief', 'to', 'order', 'prepare-at', 'threshold', 'wait-minutes', 'countdown'],
|
|
184
185
|
},
|
|
185
186
|
ui: { flags: ['no-open', 'background', 'stop', 'serve-detached'], valued: ['port'] },
|
|
186
187
|
app: { flags: ['json'], valued: ['url'] },
|
|
@@ -35,6 +35,8 @@ export interface ReadyFile {
|
|
|
35
35
|
/** The Brief file, when the session or the user wrote one. */
|
|
36
36
|
brief?: string;
|
|
37
37
|
briefSource?: 'agent' | 'user';
|
|
38
|
+
/** `sideby handoff ready --to`: the Account ref to hand over to. */
|
|
39
|
+
to?: string;
|
|
38
40
|
sessionId?: string;
|
|
39
41
|
transcriptPath?: string;
|
|
40
42
|
}
|
|
@@ -68,7 +70,8 @@ export declare function readJsonFile<T>(path: string): Promise<T | null>;
|
|
|
68
70
|
export declare function writeJsonFile(path: string, value: unknown): Promise<void>;
|
|
69
71
|
/**
|
|
70
72
|
* `sideby handoff ready` (spec §3.17): asks the parent to hand over after this turn. Works only inside a run sideby
|
|
71
|
-
* started with Auto Handoff; `briefFile`, when given, is copied into the chain directory (mode 600)
|
|
73
|
+
* started with Auto Handoff; `briefFile`, when given, is copied into the chain directory (mode 600); `to` is the
|
|
74
|
+
* Account ref to hand over to.
|
|
72
75
|
* Returns the request, or null outside such a run.
|
|
73
76
|
*/
|
|
74
|
-
export declare function requestHandoff(env: Readonly<Record<string, string | undefined>>, briefFile?: string): Promise<ReadyFile | null>;
|
|
77
|
+
export declare function requestHandoff(env: Readonly<Record<string, string | undefined>>, briefFile?: string, to?: string): Promise<ReadyFile | null>;
|
|
@@ -50,10 +50,11 @@ export async function writeJsonFile(path, value) {
|
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* `sideby handoff ready` (spec §3.17): asks the parent to hand over after this turn. Works only inside a run sideby
|
|
53
|
-
* started with Auto Handoff; `briefFile`, when given, is copied into the chain directory (mode 600)
|
|
53
|
+
* started with Auto Handoff; `briefFile`, when given, is copied into the chain directory (mode 600); `to` is the
|
|
54
|
+
* Account ref to hand over to.
|
|
54
55
|
* Returns the request, or null outside such a run.
|
|
55
56
|
*/
|
|
56
|
-
export async function requestHandoff(env, briefFile) {
|
|
57
|
+
export async function requestHandoff(env, briefFile, to) {
|
|
57
58
|
const runId = env[HANDOFF_ENV.run];
|
|
58
59
|
const dir = env[HANDOFF_ENV.dir];
|
|
59
60
|
if (!runId || !dir)
|
|
@@ -68,6 +69,7 @@ export async function requestHandoff(env, briefFile) {
|
|
|
68
69
|
at: new Date().toISOString(),
|
|
69
70
|
trigger: 'manual',
|
|
70
71
|
...(brief ? { brief, briefSource: 'user' } : {}),
|
|
72
|
+
...(to ? { to } : {}),
|
|
71
73
|
};
|
|
72
74
|
await writeJsonFile(join(dir, FILES.request), request);
|
|
73
75
|
return request;
|
|
@@ -28,6 +28,6 @@ opts?: {
|
|
|
28
28
|
}): Promise<number | null>;
|
|
29
29
|
/**
|
|
30
30
|
* For `sideby handoff ready`: where a Handoff from the session running under `env` would go now, using the chain's
|
|
31
|
-
* Accounts so far; null when `env` is not a run sideby started with Auto Handoff.
|
|
31
|
+
* Accounts so far (or to `to` only); null when `env` is not a run sideby started with Auto Handoff.
|
|
32
32
|
*/
|
|
33
|
-
export declare function readyDecision(rt: Runtime, env: Env): Promise<HandoffDecision | null>;
|
|
33
|
+
export declare function readyDecision(rt: Runtime, env: Env, to?: string): Promise<HandoffDecision | null>;
|
|
@@ -131,9 +131,9 @@ export function hookEntryWorks(env) {
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
/** The decision, or `none` with the reason when sideby cannot tell (an account removed mid-chain, say). */
|
|
134
|
-
async function decide(rt, account, used) {
|
|
134
|
+
async function decide(rt, account, used, to) {
|
|
135
135
|
try {
|
|
136
|
-
return await rt.handoffDecision(account, used);
|
|
136
|
+
return await rt.handoffDecision(account, used, to !== undefined ? { to } : {});
|
|
137
137
|
}
|
|
138
138
|
catch (err) {
|
|
139
139
|
return { kind: 'none', reason: err.message };
|
|
@@ -258,7 +258,7 @@ opts = {}) {
|
|
|
258
258
|
return outcome.code;
|
|
259
259
|
}
|
|
260
260
|
// Nothing to move on to (or better to wait): leave the session alone and say why once it ends.
|
|
261
|
-
const decision = await decide(rt, account, used);
|
|
261
|
+
const decision = await decide(rt, account, used, outcome.ready.to);
|
|
262
262
|
if (decision.kind !== 'pick') {
|
|
263
263
|
await rm(join(dir, FILES.ready), { force: true });
|
|
264
264
|
const code = await host.exited;
|
|
@@ -319,7 +319,7 @@ opts = {}) {
|
|
|
319
319
|
hop = {
|
|
320
320
|
ref: next.ref,
|
|
321
321
|
args: next.family === account.family ? h.continueArgs(hop.args) : [],
|
|
322
|
-
prompt: firstPrompt(account.ref, done.path),
|
|
322
|
+
prompt: firstPrompt(account.ref, done.path, ready.trigger),
|
|
323
323
|
};
|
|
324
324
|
n++;
|
|
325
325
|
}
|
|
@@ -340,14 +340,14 @@ async function noteUnhandedHighQuota(rt, account, used, threshold, io) {
|
|
|
340
340
|
}
|
|
341
341
|
/**
|
|
342
342
|
* For `sideby handoff ready`: where a Handoff from the session running under `env` would go now, using the chain's
|
|
343
|
-
* Accounts so far; null when `env` is not a run sideby started with Auto Handoff.
|
|
343
|
+
* Accounts so far (or to `to` only); null when `env` is not a run sideby started with Auto Handoff.
|
|
344
344
|
*/
|
|
345
|
-
export async function readyDecision(rt, env) {
|
|
345
|
+
export async function readyDecision(rt, env, to) {
|
|
346
346
|
const dir = env[HANDOFF_ENV.dir];
|
|
347
347
|
const ref = env.SIDEBY_ACCOUNT;
|
|
348
348
|
if (!env[HANDOFF_ENV.run] || !dir || !ref)
|
|
349
349
|
return null;
|
|
350
350
|
const account = await rt.resolve(ref);
|
|
351
351
|
const chain = await readJsonFile(join(dir, FILES.chain));
|
|
352
|
-
return decide(rt, account, chain?.hops.map((h) => h.ref) ?? [account.ref]);
|
|
352
|
+
return decide(rt, account, chain?.hops.map((h) => h.ref) ?? [account.ref], to);
|
|
353
353
|
}
|
|
@@ -50,4 +50,9 @@ export declare function selectNext(input: {
|
|
|
50
50
|
now: Date;
|
|
51
51
|
/** Decide now what happens when the origin's fullest window reaches the threshold (see `waitUntil`). */
|
|
52
52
|
ahead?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* `sideby handoff ready --to`: the only candidate, which may be an Account this chain already used (handing back to
|
|
55
|
+
* an Account whose Quota came back); every other rule still applies.
|
|
56
|
+
*/
|
|
57
|
+
to?: string;
|
|
53
58
|
}): HandoffDecision;
|
|
@@ -52,13 +52,19 @@ function stateOf(c, s, now) {
|
|
|
52
52
|
}
|
|
53
53
|
export function selectNext(input) {
|
|
54
54
|
const { origin, settings: s, now } = input;
|
|
55
|
+
const refOf = (name) => input.aliases[name] ?? name;
|
|
56
|
+
const byRef = new Map(input.candidates.map((c) => [c.ref, c]));
|
|
57
|
+
if (input.to !== undefined) {
|
|
58
|
+
const target = byRef.get(refOf(input.to));
|
|
59
|
+
if (!target)
|
|
60
|
+
return { kind: 'none', reason: `${input.to} is not an account sideby can find` };
|
|
61
|
+
return toTarget(origin, target, s, new Set(s.crossOrganization.map(refOf)), now);
|
|
62
|
+
}
|
|
55
63
|
const until = waitUntil(origin, s, now, input.ahead === true);
|
|
56
64
|
if (until)
|
|
57
65
|
return { kind: 'wait', until };
|
|
58
|
-
const refOf = (name) => input.aliases[name] ?? name;
|
|
59
66
|
const allowed = new Set(s.crossOrganization.map(refOf));
|
|
60
67
|
const used = new Set([origin.ref, ...input.used]);
|
|
61
|
-
const byRef = new Map(input.candidates.map((c) => [c.ref, c]));
|
|
62
68
|
const family = s.families[origin.family];
|
|
63
69
|
let list;
|
|
64
70
|
if (family?.policy === 'order') {
|
|
@@ -100,3 +106,32 @@ export function selectNext(input) {
|
|
|
100
106
|
...withSkipped,
|
|
101
107
|
};
|
|
102
108
|
}
|
|
109
|
+
/** Why `target` may not take over from `origin`, or null when it may; the same rules as `selectNext` but `used`. */
|
|
110
|
+
function refusal(origin, target, s, allowed) {
|
|
111
|
+
if (target.ref === origin.ref)
|
|
112
|
+
return 'it is the account this session runs in';
|
|
113
|
+
if (origin.sameLogin?.includes(target.ref))
|
|
114
|
+
return `it is the same login as ${origin.ref}, so it shares the same quota`;
|
|
115
|
+
if (!target.installed)
|
|
116
|
+
return 'its host is not installed';
|
|
117
|
+
if (!target.receive)
|
|
118
|
+
return 'it cannot take over a task';
|
|
119
|
+
if (!s.sameFamily && target.family === origin.family && target.kind !== 'api')
|
|
120
|
+
return 'handing over within one host is off; turn on `handoff.sameFamily`';
|
|
121
|
+
if (!allowed.has(target.ref) && !sameOrganization(origin.identity, target.identity))
|
|
122
|
+
return `it is signed in to another organization; add it to \`handoff.crossOrganization\` to allow it`;
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function toTarget(origin, target, s, allowed, now) {
|
|
126
|
+
const why = refusal(origin, target, s, allowed);
|
|
127
|
+
if (why)
|
|
128
|
+
return { kind: 'none', reason: `${target.ref} may not take over: ${why}` };
|
|
129
|
+
const e = stateOf(target, s, now);
|
|
130
|
+
if (e.state === 'ready' || e.state === 'unknown' || e.state === 'api')
|
|
131
|
+
return { kind: 'pick', ref: target.ref };
|
|
132
|
+
return {
|
|
133
|
+
kind: 'none',
|
|
134
|
+
reason: `${target.ref} cannot take over now: ${e.state === 'full' ? 'its quota is full' : 'it is signed out'}`,
|
|
135
|
+
...(e.state === 'full' && e.resetsAt ? { earliestReset: { ref: target.ref, at: e.resetsAt } } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -6,12 +6,14 @@ import { editConfigAccounts } from './config.js';
|
|
|
6
6
|
import { UserError } from './errors.js';
|
|
7
7
|
import { lineDiff } from './handoff-status.js';
|
|
8
8
|
function pick(key, sideby, host, last) {
|
|
9
|
+
// What the Account really ran last beats the Host's default: subscription Accounts often share one settings.json,
|
|
10
|
+
// so its value would show the same for all of them.
|
|
9
11
|
if (sideby[key])
|
|
10
12
|
return { value: sideby[key], from: 'sideby' };
|
|
11
|
-
if (host[key])
|
|
12
|
-
return { value: host[key], from: 'host' };
|
|
13
13
|
if (last[key])
|
|
14
14
|
return { value: last[key], from: 'last' };
|
|
15
|
+
if (host[key])
|
|
16
|
+
return { value: host[key], from: 'host' };
|
|
15
17
|
return undefined;
|
|
16
18
|
}
|
|
17
19
|
/** `last` is the model and effort of the newest session, from the usage reader when the caller already has it. */
|
|
@@ -3,5 +3,11 @@ export declare function thresholdMessage(brief: string, pressure: number): strin
|
|
|
3
3
|
/** At the threshold when the session cannot write a Brief: sideby puts one together from its records. */
|
|
4
4
|
export declare function endTurnMessage(pressure: number): string;
|
|
5
5
|
export declare function stopMessage(brief: string): string;
|
|
6
|
+
declare const WHY: {
|
|
7
|
+
readonly threshold: 'its quota ran low';
|
|
8
|
+
readonly limit: 'its quota ran out';
|
|
9
|
+
readonly manual: 'the user asked to hand over';
|
|
10
|
+
};
|
|
6
11
|
/** The next session's first prompt: the Brief's path only, never its text (it would show in a process list). */
|
|
7
|
-
export declare function firstPrompt(fromRef: string, brief: string): string;
|
|
12
|
+
export declare function firstPrompt(fromRef: string, brief: string, trigger: keyof typeof WHY): string;
|
|
13
|
+
export {};
|
|
@@ -21,7 +21,12 @@ export function endTurnMessage(pressure) {
|
|
|
21
21
|
export function stopMessage(brief) {
|
|
22
22
|
return `sideby: before you stop, update the handoff brief at ${brief} (${TEMPLATE}; ${RULES}), then end your turn.`;
|
|
23
23
|
}
|
|
24
|
+
const WHY = {
|
|
25
|
+
threshold: 'its quota ran low',
|
|
26
|
+
limit: 'its quota ran out',
|
|
27
|
+
manual: 'the user asked to hand over',
|
|
28
|
+
};
|
|
24
29
|
/** The next session's first prompt: the Brief's path only, never its text (it would show in a process list). */
|
|
25
|
-
export function firstPrompt(fromRef, brief) {
|
|
26
|
-
return `This task was handed over from ${fromRef} by sideby because
|
|
30
|
+
export function firstPrompt(fromRef, brief, trigger) {
|
|
31
|
+
return `This task was handed over from ${fromRef} by sideby because ${WHY[trigger]}. Read ${brief} first, then continue the task it describes. If the user asks to move the task to another account (back to ${fromRef}, say), run \`sideby handoff ready --to <account>\` and end your turn.`;
|
|
27
32
|
}
|
package/dist/src/panel/i18n.d.ts
CHANGED
package/dist/src/panel/i18n.js
CHANGED
|
@@ -258,6 +258,7 @@ const en = {
|
|
|
258
258
|
'md.from.sideby': 'set by sideby for this account',
|
|
259
259
|
'md.from.host': 'from {host}’s settings',
|
|
260
260
|
'md.from.last': 'used in the last session',
|
|
261
|
+
'md.newDefault': 'new sessions start with {value} ({from})',
|
|
261
262
|
'md.none': 'not known yet',
|
|
262
263
|
'md.click': 'Click to change.',
|
|
263
264
|
'md.envNote': 'ANTHROPIC_MODEL or CLAUDE_CODE_EFFORT_LEVEL in your shell, and settings from your organization, override this; sideby cannot see them.',
|
|
@@ -720,6 +721,7 @@ const zh = {
|
|
|
720
721
|
'md.from.sideby': 'sideby 为这个账号设置',
|
|
721
722
|
'md.from.host': '来自 {host} 的设置',
|
|
722
723
|
'md.from.last': '最近一次会话用的',
|
|
724
|
+
'md.newDefault': '新会话默认是 {value}({from})',
|
|
723
725
|
'md.none': '暂时不知道',
|
|
724
726
|
'md.click': '点击修改。',
|
|
725
727
|
'md.envNote': 'shell 里的 ANTHROPIC_MODEL、CLAUDE_CODE_EFFORT_LEVEL 和组织下发的设置会覆盖这里,sideby 看不到它们。',
|
|
@@ -2079,10 +2079,21 @@ const MAIN = String.raw `
|
|
|
2079
2079
|
function modelChip(a) {
|
|
2080
2080
|
const mi = a.modelInfo
|
|
2081
2081
|
if (!mi || (!mi.model && !mi.effort)) return null
|
|
2082
|
-
|
|
2082
|
+
// The row has little room; the hover text keeps the full model id.
|
|
2083
|
+
const text = [mi.model && mi.model.value.replace(/^claude-/, ''), mi.effort && mi.effort.value].filter(Boolean).join(' · ')
|
|
2084
|
+
// A value from the last session may differ from what a new session starts with; say both.
|
|
2085
|
+
const line = (label, v, key) => {
|
|
2086
|
+
if (!v) return null
|
|
2087
|
+
const out = label + ': ' + v.value + ' (' + modelFrom(a, v.from) + ')'
|
|
2088
|
+
const host = mi.host && mi.host[key]
|
|
2089
|
+
// 'opus' and 'claude-opus-5-5' name the same model: only a real difference is worth a line.
|
|
2090
|
+
return v.from === 'last' && host && !v.value.includes(host)
|
|
2091
|
+
? out + '; ' + t('md.newDefault', { value: host, from: modelFrom(a, 'host') })
|
|
2092
|
+
: out
|
|
2093
|
+
}
|
|
2083
2094
|
const title = [
|
|
2084
|
-
|
|
2085
|
-
|
|
2095
|
+
line(t('md.model'), mi.model, 'model'),
|
|
2096
|
+
line(t('md.effort'), mi.effort, 'effort'),
|
|
2086
2097
|
mi.settable ? t('md.click') : null,
|
|
2087
2098
|
].filter(Boolean).join('\n')
|
|
2088
2099
|
if (!mi.settable) return h('span', { class: 'mono', title }, text)
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -239,10 +239,11 @@ export interface Runtime {
|
|
|
239
239
|
/**
|
|
240
240
|
* Where an Auto Handoff from `origin` would go now (spec §3.17 "选号"): every Account of every Family is a
|
|
241
241
|
* candidate; `used` lists the chain's Accounts so far. `ahead` decides early what happens once the origin's fullest
|
|
242
|
-
* window reaches the threshold, as plan.json and the Panel need.
|
|
242
|
+
* window reaches the threshold, as plan.json and the Panel need. `to` names the only candidate (`handoff ready --to`).
|
|
243
243
|
*/
|
|
244
244
|
handoffDecision(origin: Account, used: readonly string[], opts?: {
|
|
245
245
|
ahead?: boolean;
|
|
246
|
+
to?: string;
|
|
246
247
|
}): Promise<HandoffDecision>;
|
|
247
248
|
readContext(): ReadContext;
|
|
248
249
|
}
|
package/dist/src/runtime.js
CHANGED
package/package.json
CHANGED
package/skills/sideby/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: sideby
|
|
3
3
|
description: Use when the user runs, or wants to run, several AI coding accounts (Claude Code, Codex, Grok Build, pi) side by side. Covers installing sideby and setting up a new account ("install sideby", "set up another Claude account", "add cc008", "配置一个 cc00x", "再加一个 Codex 账号"), explaining what accounts share and what stays separate ("what do my accounts share?", "这些账号共享了什么"), showing which account a session is ("show [cc001] in the status line", "加上账号标识"), seeing accounts, quota and usage, moving on when one hits its 5-hour or weekly limit, adding a short command (alias), and repairing drifted account directories (skills, hooks, rules or settings not shared, wrong symlinks, credential file permissions). Drives the `sideby` CLI with `--json`; the agent does every step except browser sign-in, filling in secrets and starting interactive sessions, which it hands to the user as one command.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "0.9.
|
|
5
|
+
version: "0.9.2"
|
|
6
6
|
requires:
|
|
7
7
|
bins: [sideby, node]
|
|
8
8
|
cliHelp: sideby --help
|
|
@@ -41,7 +41,7 @@ With config `"handoff": { "auto": true }`, a session sideby started hands its ta
|
|
|
41
41
|
|
|
42
42
|
- When sideby tells you the quota is at 80%, write the handoff brief at the path it names and keep it current (do not tell the user that another account will take over; sideby decides that and says so itself): the goal, what is done, the remaining steps, key decisions and why, the files that matter. Reference specs, ADRs, commits and diffs by path; list the skills the next agent should use; never include keys, passwords or personal data.
|
|
43
43
|
- When it says the quota is at 95%, finish the current step, update the brief, and end your turn. sideby starts the next account from it.
|
|
44
|
-
- When the user asks to hand over now, update the brief, run `sideby handoff ready` (add `--brief <file>` when the user's own handoff tool wrote one), and end your turn.
|
|
44
|
+
- When the user asks to hand over now, update the brief, run `sideby handoff ready` (add `--brief <file>` when the user's own handoff tool wrote one), and end your turn. Tell the user the account it prints, not what `sideby next` says: the chain never goes back to an account it already used. When the user names an account ("go back to cc008"), run `sideby handoff ready --to <account>`; it works for an account this chain already used.
|
|
45
45
|
|
|
46
46
|
### Turning it on for the user
|
|
47
47
|
|