@onepointfour-packs/flaredeck-agent 0.2.202 → 0.2.204
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/agent.d.ts +2 -1
- package/dist/agent.js +13 -3
- package/dist/agent.js.map +1 -1
- package/dist/bootstrap-repo.js +3 -1
- package/dist/bootstrap-repo.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js.map +1 -1
- package/dist/flaredeck-proxy.d.ts +7 -0
- package/dist/flaredeck-proxy.js +9 -0
- package/dist/flaredeck-proxy.js.map +1 -1
- package/dist/hydrate.d.ts +16 -0
- package/dist/hydrate.js +3 -1
- package/dist/hydrate.js.map +1 -1
- package/dist/jevguard.d.ts +3 -0
- package/dist/jevguard.js +10 -0
- package/dist/jevguard.js.map +1 -1
- package/dist/serve.js +49 -18
- package/dist/serve.js.map +1 -1
- package/dist/trusted-scripts.d.ts +34 -0
- package/dist/trusted-scripts.js +85 -0
- package/dist/trusted-scripts.js.map +1 -0
- package/dist/worktree.d.ts +15 -0
- package/dist/worktree.js +29 -4
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -9,6 +9,7 @@ import { freshGitHubToken } from './githubauth.js';
|
|
|
9
9
|
import { answerFrom, createPresence, heldFor, modeCommand } from './presence.js';
|
|
10
10
|
import { defaultUploadRoots, fileTools, flaredeckStores } from './filestore.js';
|
|
11
11
|
import { scratchFor } from './cardmess.js';
|
|
12
|
+
import { locateScripts } from './trusted-scripts.js';
|
|
12
13
|
import { boardsNaming, fetchLadder, isSoloWorker, namedOnLadders, releaseTools } from './release.js';
|
|
13
14
|
import { LiveFeed, liveSender, midTurnMessage, ASSISTANT_KEY, claudeProjectDir, historyMessage, recentExchanges, latestCliSession, assistantBrief, assistantIgnores, assistantPerson, asAssistantEvent, loadAssistantSession, saveAssistantSession } from './assistant.js';
|
|
14
15
|
import { codePrefix, loadPrefixes, savePrefixes, SCRATCH_SWEEP_EVERY_MS, sweepScratch } from './scratch.js';
|
|
@@ -42,7 +43,7 @@ export function neverStarted(e) {
|
|
|
42
43
|
}
|
|
43
44
|
import { isJustReading, jevGuard, judgeCommand } from './jevguard.js';
|
|
44
45
|
import { loadProviders } from './providers.js';
|
|
45
|
-
import { cardWorkOnBase, currentBranch, ensureWorktree, finishRefusalStands, freeDiskBytes, headOf, leadGaveItBack, sweepWorktrees, releaseSharedPushGuard, existingWorktree, produced, releaseWorktree, tooEarlyToFinish, uncommittedCount, worktreeBrief } from './worktree.js';
|
|
46
|
+
import { cardWorkOnBase, currentBranch, ensureWorktree, finishRefusalStands, freeDiskBytes, headOf, leadGaveItBack, useGitHubToken, sweepWorktrees, releaseSharedPushGuard, existingWorktree, produced, releaseWorktree, tooEarlyToFinish, uncommittedCount, worktreeBrief } from './worktree.js';
|
|
46
47
|
import { askingForComponent, checkouts, repoIdentity, resolveCheckout, voteByCode, workspaceBrief, describeCheckout } from './workspace.js';
|
|
47
48
|
import { chooseRepoWithJev } from './jev.js';
|
|
48
49
|
import { ROUTE, intake, intakeText, unclearBrief } from './intake.js';
|
|
@@ -68,7 +69,7 @@ import { mergeableComments, combineComments, unparkReason, isBossInstruction, is
|
|
|
68
69
|
import { LogShipper } from './log-ship.js';
|
|
69
70
|
import { isOwnMove } from './own-moves.js';
|
|
70
71
|
import { baseBranchesFromComponent } from './workspace.js';
|
|
71
|
-
import { isAfterReleaseMove } from './flaredeck-proxy.js';
|
|
72
|
+
import { isAfterReleaseMove, isReleasedClose } from './flaredeck-proxy.js';
|
|
72
73
|
import { canPublish, canPushTo } from './githubauth.js';
|
|
73
74
|
/**
|
|
74
75
|
* The always-on agent.
|
|
@@ -525,6 +526,7 @@ export async function serve(opts) {
|
|
|
525
526
|
log('tool', gh ? `GitHub: using FlareDeck's connection (@${gh.login ?? 'unknown'})` : 'GitHub: this machine\'s own login');
|
|
526
527
|
}
|
|
527
528
|
flaredeckGithub = gh;
|
|
529
|
+
useGitHubToken(gh?.token);
|
|
528
530
|
/* Jam for intake: the workspace's jam connection, asked for again on each read (its token
|
|
529
531
|
is refreshed in FlareDeck). Found by URL as well as name, so a connection named
|
|
530
532
|
differently still counts. */
|
|
@@ -541,6 +543,19 @@ export async function serve(opts) {
|
|
|
541
543
|
setProvidersFromFlaredeck(hydrated.providers);
|
|
542
544
|
const { setSecretsFromFlaredeck } = await import('./secrets.js');
|
|
543
545
|
setSecretsFromFlaredeck(hydrated.secrets);
|
|
546
|
+
// Trusted scripts, found in this machine's checkouts: this folder, its siblings, card clones.
|
|
547
|
+
{
|
|
548
|
+
const seen = new Map();
|
|
549
|
+
for (const root of [config.repo, dirname(config.repo), cardReposRoot()])
|
|
550
|
+
for (const c of await checkouts(root).catch(() => []))
|
|
551
|
+
seen.set(c.path, c);
|
|
552
|
+
const found = locateScripts(hydrated.allowedScripts ?? [], [...seen.values()]);
|
|
553
|
+
const was = (config.trustedScripts ?? []).map((t) => t.abs).join('|');
|
|
554
|
+
config.trustedScripts = found;
|
|
555
|
+
config.otherScripts = hydrated.otherScripts ?? [];
|
|
556
|
+
if (found.map((t) => t.abs).join('|') !== was && found.length)
|
|
557
|
+
log('tool', `trusted scripts: ${found.map((t) => t.path).join(', ')}`);
|
|
558
|
+
}
|
|
544
559
|
// Skills shared from FlareDeck, kept current the same way. See skills.ts.
|
|
545
560
|
const { flaredeckSkills, syncSkills } = await import('./skills.js');
|
|
546
561
|
const plugin = await syncSkills(join(configDir(), 'skills', slug(config.repo)), flaredeckSkills(config.mcpUrl, tenantForMcp, () => opts.auth(), (config.boards ?? []).map((b) => b.key)), (t, warn) => log(warn ? 'warn' : 'tool', t));
|
|
@@ -868,6 +883,7 @@ export async function serve(opts) {
|
|
|
868
883
|
repo: config.repo,
|
|
869
884
|
card: () => onCard?.card ?? null,
|
|
870
885
|
cwd: () => reachable?.cwd ?? job?.cwd ?? null,
|
|
886
|
+
trusted: () => config.trustedScripts ?? [],
|
|
871
887
|
access: () => {
|
|
872
888
|
const out = [];
|
|
873
889
|
if (reachable?.cwd)
|
|
@@ -1041,10 +1057,17 @@ export async function serve(opts) {
|
|
|
1041
1057
|
* are assigned to it). Both have to be true: assigned to it, on a board it releases for.
|
|
1042
1058
|
*/
|
|
1043
1059
|
let releaseBoards = new Set();
|
|
1044
|
-
|
|
1045
|
-
|
|
1060
|
+
/**
|
|
1061
|
+
* An assistant works the cards PEOPLE give it, on any board (Cobus, 20 Oct): FlareDeck refuses an
|
|
1062
|
+
* agent — a lead, a manager, another bot — that tries to assign it one, so any card it holds came
|
|
1063
|
+
* from a person. Card work is a worker's turn (its own checkout where the board has code); its
|
|
1064
|
+
* person's chat stays one conversation. Releasing is separate: only where a board names it.
|
|
1065
|
+
*/
|
|
1066
|
+
const assistantCard = (e) => isAssistant && (
|
|
1067
|
+
// Its own sweep and its own pickups are card work too: worker turns, not the chat.
|
|
1046
1068
|
(e.event === 'sweep' && e.id === 'sweep')
|
|
1047
|
-
|| (
|
|
1069
|
+
|| String(e.id ?? '').startsWith('resume-')
|
|
1070
|
+
|| (!!(e.cardKey || e.cardCode) && (e.assigneeKey === selfId || (e.event === 'assigned' && e.forYou === true))));
|
|
1048
1071
|
/**
|
|
1049
1072
|
* One person's own agent: on no team, never dispatched to, listening only to `person`. One long
|
|
1050
1073
|
* conversation, never handed over — the SDK compacts it — and loose rules. See ASSISTANT_BRIEF.
|
|
@@ -2128,8 +2151,7 @@ export async function serve(opts) {
|
|
|
2128
2151
|
await enterPause(wall);
|
|
2129
2152
|
})();
|
|
2130
2153
|
const sweepNow = () => {
|
|
2131
|
-
|
|
2132
|
-
if (paused || !sweepsEnabled || stopping || (isAssistant && !releaseBoards.size))
|
|
2154
|
+
if (paused || !sweepsEnabled || stopping)
|
|
2133
2155
|
return;
|
|
2134
2156
|
// Busy is reason enough to wait: the next tick is minutes away and the work is not going
|
|
2135
2157
|
// anywhere.
|
|
@@ -2148,7 +2170,7 @@ export async function serve(opts) {
|
|
|
2148
2170
|
// Asked here first, for nothing: a sweep used to start a whole session (28k tokens) only for
|
|
2149
2171
|
// the model to call get-outstanding, hear "nothing", and write an empty handover — four idle
|
|
2150
2172
|
// agents doing it in one half hour (19 Oct). The turn runs only when something is there.
|
|
2151
|
-
const sweepBoards =
|
|
2173
|
+
const sweepBoards = [...myBoards];
|
|
2152
2174
|
const outstanding = String(await callFlaredeckTool(config.mcpUrl, await opts.auth(), 'get-outstanding', {
|
|
2153
2175
|
...(boardArg(sweepBoards) !== undefined ? { board: boardArg(sweepBoards) } : {}),
|
|
2154
2176
|
}).catch(() => '') ?? '');
|
|
@@ -2158,7 +2180,7 @@ export async function serve(opts) {
|
|
|
2158
2180
|
}
|
|
2159
2181
|
if (working || queue.length || live.size)
|
|
2160
2182
|
return;
|
|
2161
|
-
const only = isAssistant ?
|
|
2183
|
+
const only = isAssistant ? ' — only the cards people have given you; nothing from the pile, and anything else is your person\'s to direct' : '';
|
|
2162
2184
|
if (enqueue({ id: 'sweep', event: 'sweep', line: `checking what is still assigned to you${only}` }))
|
|
2163
2185
|
void drain();
|
|
2164
2186
|
})();
|
|
@@ -2213,9 +2235,6 @@ export async function serve(opts) {
|
|
|
2213
2235
|
...[...resumedIdle].filter(([, n]) => n >= RESUME_IDLE_LIMIT).map(([c]) => c),
|
|
2214
2236
|
])];
|
|
2215
2237
|
const resumeOwnWork = async () => {
|
|
2216
|
-
// An assistant's own work is only the cards on boards it releases for; otherwise its person's asks.
|
|
2217
|
-
if (isAssistant && !releaseBoards.size)
|
|
2218
|
-
return false;
|
|
2219
2238
|
/* Not `live.size`: a session kept WARM for a follow-up is a cache, not work, and blocking on
|
|
2220
2239
|
it meant the card in progress was never resumed within fifteen minutes of any turn. */
|
|
2221
2240
|
if (paused || !sweepsEnabled || stopping || working || queue.length)
|
|
@@ -2226,7 +2245,7 @@ export async function serve(opts) {
|
|
|
2226
2245
|
of its own waiting. */
|
|
2227
2246
|
let answer = '';
|
|
2228
2247
|
try {
|
|
2229
|
-
const pullFrom =
|
|
2248
|
+
const pullFrom = [...myBoards];
|
|
2230
2249
|
answer = String(await callFlaredeckTool(config.mcpUrl, await opts.auth(), 'pull-next-card', {
|
|
2231
2250
|
...(boardArg(pullFrom) !== undefined ? { board: boardArg(pullFrom) } : {}),
|
|
2232
2251
|
// Cards this harness will not work now: waiting on a person, or picked up with nothing new.
|
|
@@ -3043,18 +3062,25 @@ export async function serve(opts) {
|
|
|
3043
3062
|
// An assistant's card on a board it releases for: the board says which repository. A card the
|
|
3044
3063
|
// harness picked up itself carries none, and with one checkout here the turn took that one —
|
|
3045
3064
|
// FRIDAY worked HRG-0001 in the HR Goat app checkout, not the website's (20 Oct).
|
|
3065
|
+
// A board with no repository at all (a sales board): the card has no code, so no checkout — it
|
|
3066
|
+
// is worked from the assistant's own folder, with its email and calendar tools.
|
|
3067
|
+
let assistantNoCode = false;
|
|
3046
3068
|
if (isAssistant && !isAssistantChat && !(event.baseBranches ?? []).some((e) => e.repository) && event.boardKey) {
|
|
3047
3069
|
const tid = tenantFromToken(await opts.auth());
|
|
3048
3070
|
const l = tid ? await fetchLadder(config.mcpUrl, tid, event.boardKey, () => opts.auth()).catch(() => null) : null;
|
|
3049
3071
|
const urls = (l?.repositories ?? []).map((r) => r.url).filter(Boolean);
|
|
3050
3072
|
if (urls.length)
|
|
3051
3073
|
event = { ...event, baseBranches: urls.map((u) => ({ component: '', repository: u, branch: '', role: 'feature' })) };
|
|
3074
|
+
else if (l)
|
|
3075
|
+
assistantNoCode = true;
|
|
3052
3076
|
}
|
|
3053
3077
|
let where = await resolveCheckout(config.repo, event, scope, opts.agent ?? '');
|
|
3054
3078
|
// An assistant's card for a repository that is not on this machine — its own folder is another
|
|
3055
3079
|
// project (FRIDAY's is an HR Goat checkout, the website is HrGoatMarketing): cloned once, with
|
|
3056
3080
|
// FlareDeck's GitHub connection, into the harness's own folder, and worked from there.
|
|
3057
|
-
if (
|
|
3081
|
+
if (assistantNoCode)
|
|
3082
|
+
where = { kind: 'none' };
|
|
3083
|
+
else if (isAssistant && !isAssistantChat && where.kind !== 'repo') {
|
|
3058
3084
|
const cardRoot = cardReposRoot();
|
|
3059
3085
|
const gh = flaredeckGithub;
|
|
3060
3086
|
for (const url of new Set((event.baseBranches ?? []).map((e) => e.repository).filter(Boolean))) {
|
|
@@ -3185,7 +3211,7 @@ export async function serve(opts) {
|
|
|
3185
3211
|
}
|
|
3186
3212
|
// An intake review reads the card and the code; it builds nothing, so it gets no checkout. HR-1004's
|
|
3187
3213
|
// review made a full worktree (and from dev, which the board no longer uses) just to read a card.
|
|
3188
|
-
const tree = event.event === 'intake' || isLeadDispatch(event, isLead) ? null
|
|
3214
|
+
const tree = event.event === 'intake' || isLeadDispatch(event, isLead) || assistantNoCode ? null
|
|
3189
3215
|
: event.cardKey && (chosen || where.kind === 'none')
|
|
3190
3216
|
? await ensureWorktree(repo, key, (t) => log('tool', t), startFrom, config.worktreeLocalFiles)
|
|
3191
3217
|
: await worktreeTheMessageIsAbout(event, repo, (t) => log('tool', t), (code) => ensureWorktree(repo, code, (t) => log('tool', t), startFrom, config.worktreeLocalFiles));
|
|
@@ -3289,12 +3315,17 @@ export async function serve(opts) {
|
|
|
3289
3315
|
// here rather than trusted, because a turn that produced nothing still reports confidently.
|
|
3290
3316
|
const finishGate = async (toLane) => {
|
|
3291
3317
|
// A released card moving on to testing or review is the next step of the board's flow.
|
|
3292
|
-
if (
|
|
3318
|
+
if (opts.deck) {
|
|
3293
3319
|
const here = await opts.deck.card(tag || event.cardCode || event.cardKey || '').catch(() => null);
|
|
3294
|
-
|
|
3320
|
+
const released = !!here && ((Array.isArray(here.releases) && here.releases.length > 0) || !!here.deployedAt);
|
|
3321
|
+
if (here && toLane && isAfterReleaseMove(toLane, here.lane?.type, here.lane?.name, released)) {
|
|
3295
3322
|
log('tool', `moving ${here.code || tag} on to ${toLane} — it was released already, so this is the next step, not a hand-back`);
|
|
3296
3323
|
return null;
|
|
3297
3324
|
}
|
|
3325
|
+
if (here && isReleasedClose(toLane ?? '', released)) {
|
|
3326
|
+
log('tool', `closing ${here.code || tag} — its work was released already`);
|
|
3327
|
+
return null;
|
|
3328
|
+
}
|
|
3298
3329
|
}
|
|
3299
3330
|
const why = await tooEarlyToFinish(tree);
|
|
3300
3331
|
/* A LEAD moving a card on with nothing of its own on the branch is dispatch, not a claim of
|
|
@@ -3956,7 +3987,7 @@ export async function serve(opts) {
|
|
|
3956
3987
|
// Both only reachable in-process, so an out-of-process engine is handed them explicitly
|
|
3957
3988
|
// rather than being left unable to publish and unchecked when it says it has.
|
|
3958
3989
|
...(job ? { publish: async () => publishBranch(job) } : {}),
|
|
3959
|
-
standing: standingBrief(!!teamSet, config.style ?? process.env['FLAREDECK_AGENT_STYLE'], isAssistantChat, config.mcpServers),
|
|
3990
|
+
standing: standingBrief(!!teamSet, config.style ?? process.env['FLAREDECK_AGENT_STYLE'], isAssistantChat, config.mcpServers, config.trustedScripts, config.otherScripts),
|
|
3960
3991
|
finish: finishGate,
|
|
3961
3992
|
noteCard,
|
|
3962
3993
|
keep: keepTool,
|