paseo-bm 0.2.0-alpha.1 → 0.3.0-alpha.4
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 +1 -1
- package/dist/index.js +156 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugin/client/agent-tree.ts +5 -3
- package/plugin/client/beads-header-button.ts +11 -8
- package/plugin/client/chat-card.tsx +146 -2
- package/plugin/client/chat-cards.ts +382 -6
- package/plugin/client/launch-manager.ts +6 -0
- package/plugin/client/setup-model.ts +487 -7
- package/plugin/client/setup-screen.tsx +590 -9
- package/plugin/client/waiting-pills-model.ts +74 -6
- package/plugin/client/waiting-pills.tsx +70 -26
- package/plugin/index.server.ts +49 -3
- package/plugin/roles/manager.md +12 -5
- package/plugin/roles/worker.md +20 -8
- package/plugin/server/agent-labels.ts +16 -0
- package/plugin/server/agent-role.ts +14 -3
- package/plugin/server/chat-peers.ts +3 -0
- package/plugin/server/chat-rpc.ts +7 -1
- package/plugin/server/chat-waiting.ts +63 -11
- package/plugin/server/collector.ts +11 -12
- package/plugin/server/config-writer.ts +222 -0
- package/plugin/server/cost.ts +28 -6
- package/plugin/server/dashboard-rpc.ts +72 -6
- package/plugin/server/fallback-detect.ts +183 -0
- package/plugin/server/fallback-handover.ts +365 -0
- package/plugin/server/fallback-manager.ts +170 -0
- package/plugin/server/fallback-reviewer.ts +198 -0
- package/plugin/server/fallback-rpc.ts +306 -0
- package/plugin/server/fallback-settings.ts +322 -0
- package/plugin/server/fallback-state.ts +518 -0
- package/plugin/server/fallback-switch.ts +191 -0
- package/plugin/server/fallback-wait.ts +188 -0
- package/plugin/server/manager-instructions.ts +1 -1
- package/plugin/server/manager.ts +206 -25
- package/plugin/server/model-costs.ts +238 -0
- package/plugin/server/notice-queue.ts +315 -0
- package/plugin/server/notices.ts +25 -0
- package/plugin/server/review-budget.ts +18 -9
- package/plugin/server/role-choices.ts +161 -0
- package/plugin/server/role-extras.ts +77 -4
- package/plugin/server/role-hook.ts +170 -26
- package/plugin/server/role-mode.ts +190 -11
- package/plugin/server/role-settings-rpc.ts +325 -0
- package/plugin/server/settings-notices.ts +112 -0
- package/plugin/server/setup-rpc.ts +2 -0
- package/plugin/server/setup-skills.ts +18 -3
- package/plugin/server/stop-propagation.ts +44 -32
- package/plugin/server/tools-check.ts +118 -0
- package/plugin/server/traces.ts +27 -3
- package/plugin/server/worker-instructions.ts +1 -1
- package/plugin/shared/bm-fallback.ts +91 -0
- package/plugin/shared/contracts.ts +344 -4
- package/plugin/shared/fallback-patterns.ts +201 -0
- package/plugin/shared/fallback.ts +46 -0
- package/plugin/shared/sole-worker.ts +4 -3
- package/plugin/shared/version.ts +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **Reviewer** — checks each stage and returns a verdict. It never edits anything.
|
|
10
10
|
- **Screens in Paseo** — **Metric** (what each request did and cost), **Beads** (the workspace's beads) and **Setup** (tools, skills and your own role instructions).
|
|
11
11
|
|
|
12
|
-
> **Status:** prerelease (`0.
|
|
12
|
+
> **Status:** prerelease (`0.3.0-alpha.*`). Command names, flags, exit codes and the `--json` shape are a public contract, but expect rough edges.
|
|
13
13
|
|
|
14
14
|
## Quick start
|
|
15
15
|
|
package/dist/index.js
CHANGED
|
@@ -3574,8 +3574,13 @@ var MISSING_CONFIG = (path, present, readable) => ({
|
|
|
3574
3574
|
mcpInjectIntoAgents: false,
|
|
3575
3575
|
agentProviderIds: [],
|
|
3576
3576
|
agentProfileIds: [],
|
|
3577
|
-
providerPaseoTools: {}
|
|
3577
|
+
providerPaseoTools: {},
|
|
3578
|
+
providerExtends: {},
|
|
3579
|
+
profileModels: {}
|
|
3578
3580
|
});
|
|
3581
|
+
function nonEmptyString(value) {
|
|
3582
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3583
|
+
}
|
|
3579
3584
|
var readPaseoConfigFacts = async (configFile) => {
|
|
3580
3585
|
let text;
|
|
3581
3586
|
try {
|
|
@@ -3598,8 +3603,19 @@ var readPaseoConfigFacts = async (configFile) => {
|
|
|
3598
3603
|
const providers = asRecordObject(asRecordObject(root["agents"])?.["providers"]) ?? {};
|
|
3599
3604
|
const profiles = Array.isArray(daemon?.["agentProfiles"]) ? daemon["agentProfiles"] : [];
|
|
3600
3605
|
const providerPaseoTools = {};
|
|
3606
|
+
const providerExtends = {};
|
|
3601
3607
|
for (const [id, value] of Object.entries(providers)) {
|
|
3602
|
-
|
|
3608
|
+
const provider = asRecordObject(value);
|
|
3609
|
+
providerPaseoTools[id] = paseoToolsEnabled(provider?.["paseoTools"]);
|
|
3610
|
+
const base = nonEmptyString(provider?.["extends"]);
|
|
3611
|
+
if (base !== void 0) providerExtends[id] = base;
|
|
3612
|
+
}
|
|
3613
|
+
const profileModels = {};
|
|
3614
|
+
for (const entry of profiles) {
|
|
3615
|
+
const profile = asRecordObject(entry);
|
|
3616
|
+
const id = nonEmptyString(profile?.["id"]);
|
|
3617
|
+
const model = nonEmptyString(profile?.["model"]);
|
|
3618
|
+
if (id !== void 0 && model !== void 0 && !(id in profileModels)) profileModels[id] = model;
|
|
3603
3619
|
}
|
|
3604
3620
|
return {
|
|
3605
3621
|
path: configFile,
|
|
@@ -3609,7 +3625,9 @@ var readPaseoConfigFacts = async (configFile) => {
|
|
|
3609
3625
|
mcpInjectIntoAgents: mcp?.["injectIntoAgents"] === true,
|
|
3610
3626
|
agentProviderIds: Object.keys(providers),
|
|
3611
3627
|
agentProfileIds: profiles.map((entry) => asRecordObject(entry)?.["id"]).filter((id) => typeof id === "string" && id.length > 0),
|
|
3612
|
-
providerPaseoTools
|
|
3628
|
+
providerPaseoTools,
|
|
3629
|
+
providerExtends,
|
|
3630
|
+
profileModels
|
|
3613
3631
|
};
|
|
3614
3632
|
};
|
|
3615
3633
|
var probeOwnedFiles = async (record, installHome) => {
|
|
@@ -3915,6 +3933,9 @@ function switchChecks(config) {
|
|
|
3915
3933
|
];
|
|
3916
3934
|
}
|
|
3917
3935
|
function roleChecks(record, config) {
|
|
3936
|
+
return [...presenceChecks(record, config), ...changedInAppChecks(record, config)];
|
|
3937
|
+
}
|
|
3938
|
+
function presenceChecks(record, config) {
|
|
3918
3939
|
return ROLE_NAMES.map((role) => {
|
|
3919
3940
|
const id = roleId(role);
|
|
3920
3941
|
const checkId = `role-${id}`;
|
|
@@ -3958,6 +3979,32 @@ function roleChecks(record, config) {
|
|
|
3958
3979
|
);
|
|
3959
3980
|
});
|
|
3960
3981
|
}
|
|
3982
|
+
var CHANGED_IN_APP_SEVERITY = "ok";
|
|
3983
|
+
function changedInAppChecks(record, config) {
|
|
3984
|
+
if (!config.readable) return [];
|
|
3985
|
+
const notes = [];
|
|
3986
|
+
for (const role of ROLE_NAMES) {
|
|
3987
|
+
const entry = record.roles.find((candidate) => candidate.role === role);
|
|
3988
|
+
if (entry === void 0) continue;
|
|
3989
|
+
if (!config.agentProviderIds.includes(entry.providerId) || !config.agentProfileIds.includes(entry.profileId)) {
|
|
3990
|
+
continue;
|
|
3991
|
+
}
|
|
3992
|
+
const base = config.providerExtends[entry.providerId];
|
|
3993
|
+
const model = config.profileModels[entry.profileId];
|
|
3994
|
+
const baseChanged = base !== void 0 && base !== entry.baseProvider;
|
|
3995
|
+
const modelChanged = model !== void 0 && model !== entry.model;
|
|
3996
|
+
if (!baseChanged && !modelChanged) continue;
|
|
3997
|
+
notes.push(
|
|
3998
|
+
check(
|
|
3999
|
+
"roles.changed-in-app",
|
|
4000
|
+
CHANGED_IN_APP_SEVERITY,
|
|
4001
|
+
`${roleId(role)}: base provider or model differs from what the installer wrote (changed in the app).`,
|
|
4002
|
+
`Nothing to do if the change was on purpose: installing again keeps role settings changed in the app. The installer wrote ${entry.baseProvider}/${entry.model}; Paseo's configuration has ${base ?? "(not set)"}/${model ?? "(not set)"}.`
|
|
4003
|
+
)
|
|
4004
|
+
);
|
|
4005
|
+
}
|
|
4006
|
+
return notes;
|
|
4007
|
+
}
|
|
3961
4008
|
function inventoryChecks(record) {
|
|
3962
4009
|
const versions = record.versions.length;
|
|
3963
4010
|
const active = record.versions.find((entry) => entry.active)?.version ?? "none";
|
|
@@ -4688,8 +4735,10 @@ function applyProviders(config, edit, changedPaths) {
|
|
|
4688
4735
|
const providers = isPlainObject(existing) ? existing : {};
|
|
4689
4736
|
parent[leaf] = providers;
|
|
4690
4737
|
for (const [id, value] of entries) {
|
|
4691
|
-
|
|
4692
|
-
|
|
4738
|
+
const current = providers[id];
|
|
4739
|
+
const next = mergedEntry(current, value, edit.providerMerge?.[id]);
|
|
4740
|
+
if (!deepEqual(current, next)) {
|
|
4741
|
+
providers[id] = next;
|
|
4693
4742
|
changedPaths.push(`${PROVIDERS_PATH}.${id}`);
|
|
4694
4743
|
}
|
|
4695
4744
|
}
|
|
@@ -4725,8 +4774,10 @@ function applyProfiles(config, edit, changedPaths) {
|
|
|
4725
4774
|
changedPaths.push(profileField(profile.id));
|
|
4726
4775
|
continue;
|
|
4727
4776
|
}
|
|
4728
|
-
|
|
4729
|
-
|
|
4777
|
+
const current = list[index];
|
|
4778
|
+
const next = mergedEntry(current, profile, edit.profileMerge?.[profile.id]);
|
|
4779
|
+
if (!deepEqual(current, next)) {
|
|
4780
|
+
list[index] = next;
|
|
4730
4781
|
changed = true;
|
|
4731
4782
|
changedPaths.push(profileField(profile.id));
|
|
4732
4783
|
}
|
|
@@ -4747,11 +4798,45 @@ function applyProfiles(config, edit, changedPaths) {
|
|
|
4747
4798
|
function profileField(id) {
|
|
4748
4799
|
return `${AGENT_PROFILES_PATH}[${id}]`;
|
|
4749
4800
|
}
|
|
4801
|
+
function mergedEntry(current, entry, rule) {
|
|
4802
|
+
if (!isPlainObject(current) || !isPlainObject(entry)) {
|
|
4803
|
+
return structuredClone(entry);
|
|
4804
|
+
}
|
|
4805
|
+
const merged = structuredClone(current);
|
|
4806
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
4807
|
+
const mode = rule?.keys?.[key] ?? "set";
|
|
4808
|
+
const existing = merged[key];
|
|
4809
|
+
switch (mode) {
|
|
4810
|
+
case "create-only":
|
|
4811
|
+
break;
|
|
4812
|
+
case "fill":
|
|
4813
|
+
if (existing === void 0 || existing === null) {
|
|
4814
|
+
merged[key] = structuredClone(value);
|
|
4815
|
+
}
|
|
4816
|
+
break;
|
|
4817
|
+
case "merge":
|
|
4818
|
+
merged[key] = isPlainObject(existing) && isPlainObject(value) ? { ...existing, ...structuredClone(value) } : structuredClone(value);
|
|
4819
|
+
break;
|
|
4820
|
+
case "set":
|
|
4821
|
+
merged[key] = structuredClone(value);
|
|
4822
|
+
break;
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
const drop = rule?.drop;
|
|
4826
|
+
if (drop !== void 0 && !deepEqual(current[drop.whenChanged], merged[drop.whenChanged])) {
|
|
4827
|
+
for (const key of drop.keys) {
|
|
4828
|
+
delete merged[key];
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
return merged;
|
|
4832
|
+
}
|
|
4750
4833
|
function assertOnlyOurKeys(edit) {
|
|
4751
4834
|
const ids = [
|
|
4752
4835
|
...Object.keys(edit.providers ?? {}),
|
|
4836
|
+
...Object.keys(edit.providerMerge ?? {}),
|
|
4753
4837
|
...edit.removeProviders ?? [],
|
|
4754
4838
|
...(edit.profiles ?? []).map((profile) => profile.id),
|
|
4839
|
+
...Object.keys(edit.profileMerge ?? {}),
|
|
4755
4840
|
...edit.removeProfiles ?? []
|
|
4756
4841
|
];
|
|
4757
4842
|
for (const path of edit.removeEmptyContainers ?? []) {
|
|
@@ -6776,6 +6861,8 @@ var PROVIDER_LOGIN_COMMANDS = {
|
|
|
6776
6861
|
codex: { executable: "codex", args: ["login"] },
|
|
6777
6862
|
opencode: { executable: "opencode", args: ["providers", "login"] }
|
|
6778
6863
|
};
|
|
6864
|
+
var PI_PROVIDER_ID = "pi";
|
|
6865
|
+
var PI_SIGN_IN_GUIDANCE = "Pi has no login command paseo-bm knows; sign in the way Pi's own documentation describes, then run doctor.";
|
|
6779
6866
|
function loginCommandFor(providerId) {
|
|
6780
6867
|
return Object.prototype.hasOwnProperty.call(PROVIDER_LOGIN_COMMANDS, providerId) ? PROVIDER_LOGIN_COMMANDS[providerId] : void 0;
|
|
6781
6868
|
}
|
|
@@ -6864,6 +6951,22 @@ async function ensureProviderLogins(options) {
|
|
|
6864
6951
|
}
|
|
6865
6952
|
const providers = [];
|
|
6866
6953
|
for (const [provider, roles] of rolesByProvider) {
|
|
6954
|
+
if (provider === PI_PROVIDER_ID) {
|
|
6955
|
+
print(PI_SIGN_IN_GUIDANCE);
|
|
6956
|
+
providers.push({
|
|
6957
|
+
provider,
|
|
6958
|
+
roles,
|
|
6959
|
+
before: "unknown",
|
|
6960
|
+
after: "unknown",
|
|
6961
|
+
command: null,
|
|
6962
|
+
outcome: "unknown",
|
|
6963
|
+
ran: false,
|
|
6964
|
+
run: null,
|
|
6965
|
+
manualInstructions: [PI_SIGN_IN_GUIDANCE],
|
|
6966
|
+
warning: null
|
|
6967
|
+
});
|
|
6968
|
+
continue;
|
|
6969
|
+
}
|
|
6867
6970
|
const before = (await checkProviderLogin(options.runner, provider)).state;
|
|
6868
6971
|
const login = loginCommandFor(provider);
|
|
6869
6972
|
const command = login === void 0 ? null : formatLoginCommand(login);
|
|
@@ -6965,6 +7068,32 @@ function roleProfileEntry(selection) {
|
|
|
6965
7068
|
notes: ROLE_PROFILE_NOTES[selection.role]
|
|
6966
7069
|
};
|
|
6967
7070
|
}
|
|
7071
|
+
function isNewRoleChoice(selection) {
|
|
7072
|
+
return selection.source !== "record";
|
|
7073
|
+
}
|
|
7074
|
+
function roleProviderMerge(selection) {
|
|
7075
|
+
const chosen = isNewRoleChoice(selection);
|
|
7076
|
+
return {
|
|
7077
|
+
keys: {
|
|
7078
|
+
extends: chosen ? "set" : "fill",
|
|
7079
|
+
label: chosen ? "set" : "create-only",
|
|
7080
|
+
paseoTools: "merge"
|
|
7081
|
+
}
|
|
7082
|
+
};
|
|
7083
|
+
}
|
|
7084
|
+
function roleProfileMerge(selection) {
|
|
7085
|
+
const chosen = isNewRoleChoice(selection);
|
|
7086
|
+
return {
|
|
7087
|
+
keys: {
|
|
7088
|
+
id: "set",
|
|
7089
|
+
name: chosen ? "set" : "fill",
|
|
7090
|
+
provider: "set",
|
|
7091
|
+
model: chosen ? "set" : "fill",
|
|
7092
|
+
notes: "create-only"
|
|
7093
|
+
},
|
|
7094
|
+
...chosen ? { drop: { whenChanged: "model", keys: ["thinkingOptionId"] } } : {}
|
|
7095
|
+
};
|
|
7096
|
+
}
|
|
6968
7097
|
var RoleRegistrationError = class extends Error {
|
|
6969
7098
|
constructor(message) {
|
|
6970
7099
|
super(message);
|
|
@@ -6974,7 +7103,9 @@ var RoleRegistrationError = class extends Error {
|
|
|
6974
7103
|
function roleRegistrationEdit(selections) {
|
|
6975
7104
|
const seen = /* @__PURE__ */ new Set();
|
|
6976
7105
|
const providers = {};
|
|
7106
|
+
const providerMerge = {};
|
|
6977
7107
|
const profiles = [];
|
|
7108
|
+
const profileMerge = {};
|
|
6978
7109
|
for (const selection of selections) {
|
|
6979
7110
|
if (seen.has(selection.role)) {
|
|
6980
7111
|
throw new RoleRegistrationError(
|
|
@@ -6982,10 +7113,13 @@ function roleRegistrationEdit(selections) {
|
|
|
6982
7113
|
);
|
|
6983
7114
|
}
|
|
6984
7115
|
seen.add(selection.role);
|
|
6985
|
-
|
|
7116
|
+
const id = roleId(selection.role);
|
|
7117
|
+
providers[id] = roleProviderEntry(selection);
|
|
7118
|
+
providerMerge[id] = roleProviderMerge(selection);
|
|
6986
7119
|
profiles.push(roleProfileEntry(selection));
|
|
7120
|
+
profileMerge[id] = roleProfileMerge(selection);
|
|
6987
7121
|
}
|
|
6988
|
-
return { providers, profiles };
|
|
7122
|
+
return { providers, profiles, providerMerge, profileMerge };
|
|
6989
7123
|
}
|
|
6990
7124
|
async function registerRoles(options) {
|
|
6991
7125
|
const edit = roleRegistrationEdit(options.selections);
|
|
@@ -7184,25 +7318,29 @@ function nonBlank(value) {
|
|
|
7184
7318
|
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
7185
7319
|
}
|
|
7186
7320
|
function roleActions(config, selections) {
|
|
7187
|
-
const
|
|
7188
|
-
const changed = new Set(
|
|
7189
|
-
const providers = asObject2(asObject2(config["agents"])?.["providers"]);
|
|
7190
|
-
const profiles = asObject2(config["daemon"])?.["agentProfiles"];
|
|
7321
|
+
const edited = editConfig(config, roleRegistrationEdit(selections));
|
|
7322
|
+
const changed = new Set(edited.changedPaths);
|
|
7191
7323
|
const actions = [];
|
|
7192
7324
|
for (const selection of selections) {
|
|
7193
7325
|
const id = roleId(selection.role);
|
|
7194
7326
|
const providerPath = `${PROVIDERS_PATH}.${id}`;
|
|
7195
|
-
const providerFrom = providers?.[id];
|
|
7196
7327
|
actions.push(
|
|
7197
|
-
configAction(providerPath, changed.has(providerPath),
|
|
7328
|
+
configAction(providerPath, changed.has(providerPath), providerOf(config, id), providerOf(edited.config, id) ?? null)
|
|
7198
7329
|
);
|
|
7199
7330
|
const profilePath = `${AGENT_PROFILES_PATH}[${id}]`;
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7331
|
+
actions.push(
|
|
7332
|
+
configAction(profilePath, changed.has(profilePath), profileOf(config, id), profileOf(edited.config, id) ?? null)
|
|
7333
|
+
);
|
|
7203
7334
|
}
|
|
7204
7335
|
return actions;
|
|
7205
7336
|
}
|
|
7337
|
+
function providerOf(config, id) {
|
|
7338
|
+
return asObject2(asObject2(config["agents"])?.["providers"])?.[id];
|
|
7339
|
+
}
|
|
7340
|
+
function profileOf(config, id) {
|
|
7341
|
+
const profiles = asObject2(config["daemon"])?.["agentProfiles"];
|
|
7342
|
+
return Array.isArray(profiles) ? profiles.find((entry) => asObject2(entry)?.["id"] === id) : void 0;
|
|
7343
|
+
}
|
|
7206
7344
|
function configAction(key, changes, from, to) {
|
|
7207
7345
|
const target = `paseoHome/config.json#${key}`;
|
|
7208
7346
|
if (!changes) {
|