@evandu/super-pi 0.1.166 → 0.1.167
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/client/.super-pi-client-sources.json +8 -9
- package/dist/client/assets/{CodeMirrorEditor-q5ckKYpW.js → CodeMirrorEditor-CVWde5kc.js} +2 -2
- package/dist/client/assets/{dist-YFQ3ixZU.js → dist-4fGafgy-.js} +1 -1
- package/dist/client/assets/{dist-3Qp_-Z3t.js → dist-BCl45PhR.js} +1 -1
- package/dist/client/assets/{dist-Cs1P7JrO.js → dist-BHFNgK-o.js} +1 -1
- package/dist/client/assets/{dist-BT3fZ0wT.js → dist-BNHp4wy-.js} +1 -1
- package/dist/client/assets/{dist-Crew6BC1.js → dist-BmMZE70t.js} +1 -1
- package/dist/client/assets/{dist-_pAt4bRw.js → dist-BmTCWOAf.js} +1 -1
- package/dist/client/assets/{dist-CyTiEqGG.js → dist-C44HyASP.js} +1 -1
- package/dist/client/assets/{dist-bLHDT2q5.js → dist-C5vtfmg_.js} +1 -1
- package/dist/client/assets/{dist-CBqBGuBl.js → dist-CC90sIpG.js} +1 -1
- package/dist/client/assets/{dist-DdfZJR8e.js → dist-CH83_wek.js} +1 -1
- package/dist/client/assets/{dist-CXXAxAE4.js → dist-CYucB1mz.js} +1 -1
- package/dist/client/assets/{dist-DiqGp2ie.js → dist-D7VBYFBQ.js} +1 -1
- package/dist/client/assets/{dist-B61EUjUz.js → dist-DInnP2s9.js} +1 -1
- package/dist/client/assets/{dist-dbLsO_41.js → dist-DOYAWZox.js} +1 -1
- package/dist/client/assets/{dist-B_l0KBjV.js → dist-DQ97Hq65.js} +1 -1
- package/dist/client/assets/{dist-DSIy9Ers.js → dist-DTOebxDL.js} +1 -1
- package/dist/client/assets/{dist-Bb-gT7aj.js → dist-DUgYAI4d.js} +1 -1
- package/dist/client/assets/{dist-DRHWMjx_.js → dist-DaEO5rUQ.js} +1 -1
- package/dist/client/assets/{dist-BOU0i9JW.js → dist-Dn-GoU5Q.js} +1 -1
- package/dist/client/assets/{dist-D10kcMrw.js → dist-FPYonm2j.js} +1 -1
- package/dist/client/assets/{dist-DMynScoj.js → dist-GZFbu49l.js} +1 -1
- package/dist/client/assets/{dist-BN9HZG4O.js → dist-RLM_LWKf.js} +1 -1
- package/dist/client/assets/{index-dARLbQ-8.js → index-BptmWfVR.js} +10 -10
- package/dist/client/assets/index-DWjVnvsQ.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/agents.js +9 -9
- package/dist/server/application/project-service.js +57 -6
- package/dist/server/types.d.ts +8 -1
- package/dist/shared/agent-order.d.ts +44 -0
- package/dist/shared/agent-order.js +50 -0
- package/package.json +4 -1
- package/dist/client/assets/index-C6YWfUu2.css +0 -1
package/dist/server/agents.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { orderAgentsForDisplay } from "../shared/agent-order.js";
|
|
4
5
|
/**
|
|
5
6
|
* A guard rather than a cast: it is the only thing that lets `tools:` be checked once and then used as
|
|
6
7
|
* a map everywhere without the compiler complaining. It narrows to the index-signature shape that
|
|
@@ -526,12 +527,13 @@ export async function readAgents(directory, options = {}) {
|
|
|
526
527
|
// invoked BY an agent and never chosen by a human, so listing one in the picker would promise a role
|
|
527
528
|
// the runtime cannot honour.
|
|
528
529
|
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
// package's own agents
|
|
533
|
-
//
|
|
534
|
-
// the
|
|
530
|
+
// THE ORDER IS NOT DECIDED HERE. It is `orderAgentsForDisplay` - the one rule in
|
|
531
|
+
// `src/shared/agent-order.ts`, which the client applies too. The server orders the whole catalogue it
|
|
532
|
+
// read and hands out the half that was asked for, so what leaves the server is ALREADY in display
|
|
533
|
+
// order: the package's own agents first, the conversation roles before the delegated ones, and
|
|
534
|
+
// alphabetical by id inside each. Every surface that draws a server catalogue therefore inherits that
|
|
535
|
+
// order instead of guessing it, and the one place that MERGES two catalogues re-applies the same
|
|
536
|
+
// rule rather than inventing a second one.
|
|
535
537
|
//
|
|
536
538
|
// WHY THE GROUP ORDER DOES NOT TOUCH THE OVERRIDE RULE. The rule that decides WHICH definition of
|
|
537
539
|
// an id survives was applied above, while reading: the first directory to offer an id wins, and the
|
|
@@ -539,9 +541,7 @@ export async function readAgents(directory, options = {}) {
|
|
|
539
541
|
// already survived that decision, so a user's copy of a built-in agent still wins - and it is then
|
|
540
542
|
// listed as the user's, because it IS the user's file.
|
|
541
543
|
return {
|
|
542
|
-
agents: agents
|
|
543
|
-
.filter((agent) => agent.mode === mode)
|
|
544
|
-
.sort((a, b) => (a.builtin === b.builtin ? a.id.localeCompare(b.id) : a.builtin ? -1 : 1)),
|
|
544
|
+
agents: orderAgentsForDisplay(agents).filter((agent) => agent.mode === mode),
|
|
545
545
|
problems,
|
|
546
546
|
};
|
|
547
547
|
}
|
|
@@ -158,7 +158,7 @@ const REPORT_EVENT_LIMIT = 200;
|
|
|
158
158
|
// THE WORDS THE REPORT SPEAKS IN. A reader of the report is a person, not a console, so every
|
|
159
159
|
// state and every role is written the way a person would say it out loud.
|
|
160
160
|
const PROJECT_STATUS_TEXT = {
|
|
161
|
-
planning: "草稿", active: "进行中", blocked: "已阻塞", awaiting_approval: "等待审批",
|
|
161
|
+
planning: "草稿", awaiting_start: "待启动", active: "进行中", blocked: "已阻塞", awaiting_approval: "等待审批",
|
|
162
162
|
awaiting_review: "等待复核", completed: "已完成", cancelled: "已取消", failed: "失败",
|
|
163
163
|
};
|
|
164
164
|
const TASK_STATUS_TEXT = {
|
|
@@ -475,10 +475,22 @@ export class ProjectService {
|
|
|
475
475
|
const hasLeaderBlockedTask = p.tasks.some((t) => t.status === "blocked" && isLeader(t.blockedReportedByConversationId));
|
|
476
476
|
const lastLeaderProjectReport = [...p.reports].reverse().find((r) => !r.taskId && isLeader(r.authorConversationId));
|
|
477
477
|
const leaderProjectBlocked = lastLeaderProjectReport?.kind === "blocked";
|
|
478
|
+
/* A PROJECT THAT HAS WORK REGISTERED BUT NO LEADER SESSION YET IS NOT RUNNING.
|
|
479
|
+
"active" used to mean "there is at least one task", so a project whose tasks were only
|
|
480
|
+
REGISTERED - nobody had started it, nobody was working on it - claimed on its card to be
|
|
481
|
+
running while the panel's own team section said there were no sessions at all. The card
|
|
482
|
+
and the team list contradicted each other, and the card was the one that lied: the
|
|
483
|
+
project's own status has to say what is true about the project, not what the client
|
|
484
|
+
happens to translate it into. So the state is split:
|
|
485
|
+
planning - nothing has been broken down yet,
|
|
486
|
+
awaiting_start - the work is registered and the project has NOT been started,
|
|
487
|
+
active - the project is started and a leader session exists.
|
|
488
|
+
A project has a leader session only after `start`, so the leader is the signal - not the
|
|
489
|
+
task count, and not anything the client sends. */
|
|
478
490
|
p.status = openBoss.some((e) => e.severity === "high") ? "awaiting_approval"
|
|
479
491
|
: openBoss.length || hasLeaderBlockedTask || leaderProjectBlocked ? "blocked"
|
|
480
492
|
: p.tasks.some((t) => t.status === "awaiting_review") ? "awaiting_review"
|
|
481
|
-
: p.tasks.length ? "active" : "planning";
|
|
493
|
+
: p.tasks.length ? (p.ownerConversationId ? "active" : "awaiting_start") : "planning";
|
|
482
494
|
}
|
|
483
495
|
resources(p) {
|
|
484
496
|
const state = this.store.snapshot();
|
|
@@ -502,7 +514,8 @@ export class ProjectService {
|
|
|
502
514
|
* initiator's conversation ("shared"). The leader is the root of the project's tree, so it is
|
|
503
515
|
* "project" by the same rule as everybody else - there is no special case for it. */
|
|
504
516
|
const projectTreeId = p.ownerConversationId ? this.store.conversation(p.ownerConversationId)?.treeId : undefined;
|
|
505
|
-
const
|
|
517
|
+
const memberConversations = state.conversations.filter((c) => c.workspaceId === p.workspaceId && members.has(c.id));
|
|
518
|
+
const sessions = memberConversations.map((c) => ({
|
|
506
519
|
id: c.id, title: c.title, status: c.status, tokens: tokens(c.usage), contextPercent: c.contextUsage?.percent,
|
|
507
520
|
// THE TREE SKELETON. The client draws the agents of a project as a tree, and the only
|
|
508
521
|
// thing that knows who spawned whom is the conversation's own parent link. It is sent
|
|
@@ -529,12 +542,33 @@ export class ProjectService {
|
|
|
529
542
|
Now it is counted once - through the session - and a project's budget therefore includes what
|
|
530
543
|
its one-shot calls cost, which is the whole point of a budget a person can predict.
|
|
531
544
|
See `Store.accountSubagentUsage`. */
|
|
532
|
-
|
|
545
|
+
/* THE SAME USAGE, TWICE: ONCE IN THE BILLING-EQUIVALENT UNITS THE BUDGET IS SPENT IN, AND
|
|
546
|
+
ONCE IN THE RAW 1:1 UNITS THE READER'S OWN TOOLING COUNTS IN.
|
|
547
|
+
The panel used to show only the equivalent number, and the two differ by a factor of
|
|
548
|
+
two and a half on a real run - so a reader who compared the project's cost with their
|
|
549
|
+
own dashboard saw two different numbers and no way to tell which one the panel meant.
|
|
550
|
+
So the raw components are sent as well: 输入 + 输出 + 缓存写入 + 缓存读取, which the reader
|
|
551
|
+
can add up, and can also feed into the published formula themselves.
|
|
552
|
+
The equivalent number is computed from the SUMMED components and rounded ONCE, not by
|
|
553
|
+
adding up each session's own rounded number: that way the formula printed next to the
|
|
554
|
+
number reproduces the number exactly, instead of being off by a token per session. */
|
|
555
|
+
const usage = memberConversations.reduce((acc, c) => {
|
|
556
|
+
if (!c.usage)
|
|
557
|
+
return acc;
|
|
558
|
+
acc.input += c.usage.input;
|
|
559
|
+
acc.output += c.usage.output;
|
|
560
|
+
acc.cacheRead += c.usage.cacheRead;
|
|
561
|
+
acc.cacheWrite += c.usage.cacheWrite;
|
|
562
|
+
return acc;
|
|
563
|
+
}, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
|
|
564
|
+
return { tokens: tokens(usage),
|
|
533
565
|
usageKnown: sessions.length > 0 && sessions.every((s) => Boolean(this.store.conversation(s.id)?.usage)) && runs.every((r) => Boolean(r.usage)),
|
|
534
566
|
activeWorkers: sessions.filter((s) => (!coordinatorIds.has(s.id) || (leaderIsWorker && s.id === p.ownerConversationId)) && busy(s.status)).length + runs.filter((r) => r.status === "running").length,
|
|
535
567
|
// THE PROJECT'S OWN MEMORY TREE, named once so the client does not have to infer it from
|
|
536
568
|
// the sessions. Absent only when the project has no leader session yet.
|
|
537
|
-
memoryTreeId: projectTreeId, sessions
|
|
569
|
+
memoryTreeId: projectTreeId, sessions,
|
|
570
|
+
// THE RAW 1:1 PICTURE OF THE SAME MONEY, next to the equivalent number it is spent in.
|
|
571
|
+
usage, rawTokens: usage.input + usage.output + usage.cacheRead + usage.cacheWrite };
|
|
538
572
|
}
|
|
539
573
|
list(workspaceId) {
|
|
540
574
|
const scope = this.workspace(workspaceId);
|
|
@@ -562,6 +596,12 @@ export class ProjectService {
|
|
|
562
596
|
const reason = blockedTask.blockedReason ? `(${blockedTask.blockedReason})` : "";
|
|
563
597
|
return `项目已阻塞:${blockedTask.title}${reason}`;
|
|
564
598
|
}
|
|
599
|
+
/* THE WORK IS REGISTERED AND THE PROJECT HAS NOT BEEN STARTED. The task is "ready to
|
|
600
|
+
dispatch", but dispatching needs a leader and there is no leader until the project is
|
|
601
|
+
started - so "waiting to dispatch" would point the reader at something nobody can do.
|
|
602
|
+
The headline says what is true, in the same words the card uses. */
|
|
603
|
+
if (total > 0 && !p.ownerConversationId)
|
|
604
|
+
return `已登记 ${total} 个任务,项目待启动`;
|
|
565
605
|
const inProgressTask = p.tasks.find((t) => t.status === "in_progress");
|
|
566
606
|
if (inProgressTask) {
|
|
567
607
|
return `${completed}/${total} 任务完成,正在执行${inProgressTask.title}`;
|
|
@@ -689,7 +729,12 @@ export class ProjectService {
|
|
|
689
729
|
// formula is printed next to the number: a budget nobody can predict is a budget nobody trusts.
|
|
690
730
|
// The sentence after the formula says what the number INCLUDES - a one-shot call (a sub-agent)
|
|
691
731
|
// is part of it, so a reader who dispatches children can see what that cost them.
|
|
692
|
-
|
|
732
|
+
// THE RAW 1:1 FIGURE FOLLOWS IT, because the two differ by a factor of two and a half on a
|
|
733
|
+
// real run, and a reader who checks the project's cost against their own tooling needs to
|
|
734
|
+
// see both numbers and the parts they are made of.
|
|
735
|
+
const usage = this.resources(p);
|
|
736
|
+
lines.push(`- 已用等效 Token:${usage.tokens}(口径:输入 + 输出 + 缓存写入×${CACHE_WRITE_WEIGHT} + 缓存读取×${CACHE_READ_WEIGHT},取整;含项目内的一次性调用(subagent))`);
|
|
737
|
+
lines.push(`- 原始口径(1:1):${usage.rawTokens}(输入 ${usage.usage?.input ?? 0} + 输出 ${usage.usage?.output ?? 0} + 缓存写入 ${usage.usage?.cacheWrite ?? 0} + 缓存读取 ${usage.usage?.cacheRead ?? 0})`);
|
|
693
738
|
lines.push(`- 任务:共 ${p.tasks.length} 个,已完成 ${p.tasks.filter((t) => t.status === "completed").length} 个`);
|
|
694
739
|
lines.push("", "## 交付与验收", "");
|
|
695
740
|
if (!p.tasks.length)
|
|
@@ -1192,6 +1237,12 @@ export class ProjectService {
|
|
|
1192
1237
|
throw new Error("项目完成必须提交最终交付,其他状态由任务和例外推导");
|
|
1193
1238
|
if (status === "active" && p.escalations.some((e) => e.status === "open"))
|
|
1194
1239
|
throw new Error("请先处理未解决的例外");
|
|
1240
|
+
/* A PROJECT WITH NO LEADER SESSION HAS NOTHING RUNNING IN IT. The status is derived from
|
|
1241
|
+
the project's own state, and the API may not push it back into the contradiction that
|
|
1242
|
+
derivation exists to prevent: a project whose tasks were only registered is 待启动, and
|
|
1243
|
+
no caller - the panel included - may call it 执行中 until it has actually been started. */
|
|
1244
|
+
if (status === "active" && !p.ownerConversationId)
|
|
1245
|
+
throw new Error("项目尚未启动,请先启动项目负责人");
|
|
1195
1246
|
p.status = status;
|
|
1196
1247
|
if (closed(p))
|
|
1197
1248
|
p.closedAt = stamp();
|
package/dist/server/types.d.ts
CHANGED
|
@@ -939,7 +939,7 @@ export type TerminalShell = {
|
|
|
939
939
|
file: string;
|
|
940
940
|
args: string[];
|
|
941
941
|
};
|
|
942
|
-
export type ProjectStatus = "planning" | "active" | "blocked" | "awaiting_approval" | "awaiting_review" | "completed" | "failed" | "cancelled";
|
|
942
|
+
export type ProjectStatus = "planning" | "awaiting_start" | "active" | "blocked" | "awaiting_approval" | "awaiting_review" | "completed" | "failed" | "cancelled";
|
|
943
943
|
export type ProjectTaskStatus = "pending" | "ready" | "in_progress" | "blocked" | "awaiting_review" | "completed" | "failed" | "cancelled";
|
|
944
944
|
export type ProjectRisk = "low" | "medium" | "high";
|
|
945
945
|
/**
|
|
@@ -1095,6 +1095,13 @@ export type Project = {
|
|
|
1095
1095
|
usageKnown: boolean;
|
|
1096
1096
|
activeWorkers: number;
|
|
1097
1097
|
memoryTreeId?: string;
|
|
1098
|
+
usage?: {
|
|
1099
|
+
input: number;
|
|
1100
|
+
output: number;
|
|
1101
|
+
cacheRead: number;
|
|
1102
|
+
cacheWrite: number;
|
|
1103
|
+
};
|
|
1104
|
+
rawTokens?: number;
|
|
1098
1105
|
sessions: {
|
|
1099
1106
|
id: string;
|
|
1100
1107
|
title: string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE order agent lists are shown in.
|
|
3
|
+
*
|
|
4
|
+
* WHY THERE IS A SINGLE RULE AND NOT A RULE PER SIDE. The picker's list, the settings page's grid, the
|
|
5
|
+
* sub-agent roster and the project panel's employee candidates are four views of the same catalogue, and
|
|
6
|
+
* they used to disagree about what a list of agents looks like. The server sorted what it read by
|
|
7
|
+
* "(built-in first, then id)", and the client re-sorted what the server sent by a rule of its own that
|
|
8
|
+
* added one more level - "roles before workers". A reader who saw the same agents in two orders had no
|
|
9
|
+
* way to tell which screen was wrong, because neither was: they were two answers to a question nobody
|
|
10
|
+
* had asked twice the same way.
|
|
11
|
+
*
|
|
12
|
+
* So the rule is stated once, HERE, and both sides apply it. The server imports it to order the
|
|
13
|
+
* catalogue it reads; the client imports it to order every list it draws. There is no second
|
|
14
|
+
* implementation to drift, because there is no second implementation.
|
|
15
|
+
*
|
|
16
|
+
* THE ORDER IS:
|
|
17
|
+
*
|
|
18
|
+
* 1. THE PACKAGE'S OWN agents first, then the user's. The ones that ship with Super Pi are drawn with
|
|
19
|
+
* a colour dot, and a dot scattered among the user's own reads as noise rather than as a group.
|
|
20
|
+
* 2. Inside a group, the CONVERSATION ROLES (mode: primary) before the DELEGATED ones (mode:
|
|
21
|
+
* subagent), so a list reads as "the roles, then the workers".
|
|
22
|
+
* 3. Inside a group and a role, alphabetical by id, so the order never depends on the order the
|
|
23
|
+
* filesystem happened to hand the files over in.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE FLAG IS NOT GUESSED HERE. "Is this the package's own agent?" is a question about where the app
|
|
26
|
+
* is installed, and only the server knows that: it reads the definitions and marks them. A client that
|
|
27
|
+
* tried to work it out from the directory path would be guessing, and would guess wrong the moment the
|
|
28
|
+
* package is installed somewhere unusual - which is exactly the case where the answer matters.
|
|
29
|
+
*/
|
|
30
|
+
/** The minimum an agent has to carry for this ordering to be able to place it. */
|
|
31
|
+
export type OrderableAgent = {
|
|
32
|
+
id: string;
|
|
33
|
+
mode?: "primary" | "subagent";
|
|
34
|
+
/** True when the definition came from the package's own agents directory. */
|
|
35
|
+
builtin?: boolean;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The built-in group first, then the user's, each group by role and then by id.
|
|
39
|
+
*
|
|
40
|
+
* An agent with no `builtin` flag at all (an older server, or a hand-built fixture) counts as the
|
|
41
|
+
* user's, which is the safe direction: it puts the unflagged one where a user's own would sit rather
|
|
42
|
+
* than promoting it into the group that means "this ships with Super Pi".
|
|
43
|
+
*/
|
|
44
|
+
export declare function orderAgentsForDisplay<T extends OrderableAgent>(agents: readonly T[]): T[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE order agent lists are shown in.
|
|
3
|
+
*
|
|
4
|
+
* WHY THERE IS A SINGLE RULE AND NOT A RULE PER SIDE. The picker's list, the settings page's grid, the
|
|
5
|
+
* sub-agent roster and the project panel's employee candidates are four views of the same catalogue, and
|
|
6
|
+
* they used to disagree about what a list of agents looks like. The server sorted what it read by
|
|
7
|
+
* "(built-in first, then id)", and the client re-sorted what the server sent by a rule of its own that
|
|
8
|
+
* added one more level - "roles before workers". A reader who saw the same agents in two orders had no
|
|
9
|
+
* way to tell which screen was wrong, because neither was: they were two answers to a question nobody
|
|
10
|
+
* had asked twice the same way.
|
|
11
|
+
*
|
|
12
|
+
* So the rule is stated once, HERE, and both sides apply it. The server imports it to order the
|
|
13
|
+
* catalogue it reads; the client imports it to order every list it draws. There is no second
|
|
14
|
+
* implementation to drift, because there is no second implementation.
|
|
15
|
+
*
|
|
16
|
+
* THE ORDER IS:
|
|
17
|
+
*
|
|
18
|
+
* 1. THE PACKAGE'S OWN agents first, then the user's. The ones that ship with Super Pi are drawn with
|
|
19
|
+
* a colour dot, and a dot scattered among the user's own reads as noise rather than as a group.
|
|
20
|
+
* 2. Inside a group, the CONVERSATION ROLES (mode: primary) before the DELEGATED ones (mode:
|
|
21
|
+
* subagent), so a list reads as "the roles, then the workers".
|
|
22
|
+
* 3. Inside a group and a role, alphabetical by id, so the order never depends on the order the
|
|
23
|
+
* filesystem happened to hand the files over in.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE FLAG IS NOT GUESSED HERE. "Is this the package's own agent?" is a question about where the app
|
|
26
|
+
* is installed, and only the server knows that: it reads the definitions and marks them. A client that
|
|
27
|
+
* tried to work it out from the directory path would be guessing, and would guess wrong the moment the
|
|
28
|
+
* package is installed somewhere unusual - which is exactly the case where the answer matters.
|
|
29
|
+
*/
|
|
30
|
+
/** The conversation roles come before the delegated ones, so a screen keeps reading as "roles, then workers". */
|
|
31
|
+
const modeRank = (mode) => (mode === "subagent" ? 1 : 0);
|
|
32
|
+
/**
|
|
33
|
+
* The built-in group first, then the user's, each group by role and then by id.
|
|
34
|
+
*
|
|
35
|
+
* An agent with no `builtin` flag at all (an older server, or a hand-built fixture) counts as the
|
|
36
|
+
* user's, which is the safe direction: it puts the unflagged one where a user's own would sit rather
|
|
37
|
+
* than promoting it into the group that means "this ships with Super Pi".
|
|
38
|
+
*/
|
|
39
|
+
export function orderAgentsForDisplay(agents) {
|
|
40
|
+
return [...agents].sort((a, b) => {
|
|
41
|
+
const left = a.builtin ? 0 : 1;
|
|
42
|
+
const right = b.builtin ? 0 : 1;
|
|
43
|
+
if (left !== right)
|
|
44
|
+
return left - right;
|
|
45
|
+
const roles = modeRank(a.mode) - modeRank(b.mode);
|
|
46
|
+
if (roles !== 0)
|
|
47
|
+
return roles;
|
|
48
|
+
return a.id.localeCompare(b.id);
|
|
49
|
+
});
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.167",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"scripts/stream-transport.test.mjs",
|
|
49
49
|
"scripts/remote-transport.test.mjs",
|
|
50
50
|
"scripts/browser-safe-port.test.mjs",
|
|
51
|
+
"scripts/agent-order.test.mjs",
|
|
51
52
|
"scripts/agent-picker-order.test.mjs",
|
|
52
53
|
"scripts/inline-thinking.test.mjs",
|
|
53
54
|
"scripts/inline-thinking-close-tag.test.mjs",
|
|
@@ -266,6 +267,7 @@
|
|
|
266
267
|
"scripts/sidebar-tools-browser.test.mjs",
|
|
267
268
|
"scripts/project-employee-panel-browser.test.mjs",
|
|
268
269
|
"scripts/project-evidence-panel-browser.test.mjs",
|
|
270
|
+
"scripts/project-panel-fixes-browser.test.mjs",
|
|
269
271
|
"scripts/builtin-agents-project-browser.test.mjs",
|
|
270
272
|
"scripts/tool-runtime-browser.test.mjs",
|
|
271
273
|
"scripts/session-context-menu-browser.test.mjs",
|
|
@@ -294,6 +296,7 @@
|
|
|
294
296
|
"scripts/stream-tail-browser.test.mjs",
|
|
295
297
|
"scripts/scroll-restore-browser.test.mjs",
|
|
296
298
|
"scripts/theme-contrast.test.mjs",
|
|
299
|
+
"scripts/ui-contrast-browser.test.mjs",
|
|
297
300
|
"scripts/theme-switch-state.test.mjs",
|
|
298
301
|
"scripts/settings-window-structure.test.mjs",
|
|
299
302
|
"scripts/settings-model-browser.test.mjs",
|