@rockysoft/opencode-hybrid-workflow 1.1.0 → 1.1.1
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/core/plugin.mjs +3 -0
- package/docs/CHANGELOG.md +6 -0
- package/lib/continuation-notice.mjs +5 -0
- package/lib/continuation.mjs +85 -0
- package/package.json +76 -76
- package/sidebar/reader.mjs +6 -1
- package/tui.js +86 -70
package/core/plugin.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { readDashboard } from './monitor-data.mjs';
|
|
|
9
9
|
import { DESIGN_POLICY, recordDesignRequest } from './design.mjs';
|
|
10
10
|
import { researchTool } from './research-tools.mjs';
|
|
11
11
|
import {registerImpeccableCommand,IMPECCABLE_POLICY} from '../lib/impeccable.mjs';
|
|
12
|
+
import {registerContinuation,CONTINUATION_POLICY} from '../lib/continuation.mjs';
|
|
12
13
|
|
|
13
14
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
// V2's Plugin.define is an identity function. This plain plugin needs no installed dependencies.
|
|
@@ -30,6 +31,7 @@ export function createPlugin(config, storage = config.runtimeDirectory ?? path.j
|
|
|
30
31
|
if (!controllers.has(directory)) controllers.set(directory, new Controller(directory, config.storageFor?.(directory) ?? storage, config));
|
|
31
32
|
return controllers.get(directory);
|
|
32
33
|
};
|
|
34
|
+
await registerContinuation(ctx,forSession);
|
|
33
35
|
await ctx.session.hook('prompt', async event => {
|
|
34
36
|
if(await isReviewerSession(ctx,event.sessionID))return;
|
|
35
37
|
const response = await ctx.session.get({sessionID:event.sessionID});
|
|
@@ -43,6 +45,7 @@ export function createPlugin(config, storage = config.runtimeDirectory ?? path.j
|
|
|
43
45
|
event.system.push({type:'text',text:roleText(DESIGN_POLICY)});
|
|
44
46
|
event.system.push({type:'text',text:roleText(WORKFLOW_POLICY)});
|
|
45
47
|
event.system.push({type:'text',text:IMPECCABLE_POLICY});
|
|
48
|
+
event.system.push({type:'text',text:CONTINUATION_POLICY});
|
|
46
49
|
event.system.push({type:'text',text:`${FRONTEND_COMMON}\n${FRONTEND_BUILD}`});
|
|
47
50
|
event.system.push({type:'text',text:'Hybrid recovery: completed negative/blocked code reviews allow corrections and evidence collection in fix; continue automatically and re-review, never claim acceptance. action=fix recovers a legacy completed review. New evidence in checks/images resumes paused plan/design; action=replan with concrete changed requirements opens architecture revision while preserving findings and history. action=inspect checks installed tools and CLI logins without model calls and is available during planning. Never edit bridge state or reset history. Playwright research outputs use plain filenames in .playwright-mcp/hybrid-evidence; this generated evidence is excluded from source snapshots. Navigation/resizing and screenshot capture are available while planning/designing. Supply actual PNGs to plan/design/review as images [{path,caption,artifactHash}], using status.currentArtifactHash for the code/reference being inspected. Recapture after code changes and include interaction evidence in checks. Both reviewers receive pixels; never substitute hashes or ask the user to attest images merely because they live outside Git.'});
|
|
48
51
|
});
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
- Recover reasoning-only silent stops in participating Hybrid sessions on OpenCode 2.2.5+. The same agent continues within the current execution, preserving user instructions and checkpoint approvals.
|
|
6
|
+
- Require clear plan-review, completion and blocker handoffs in agent guidance. Visible answers and user interruptions are left alone.
|
|
7
|
+
- Show a session-specific sidebar warning if recovery stops silently again without new evidence or checkpoint progress. This warning does not call a model or change approvals.
|
|
8
|
+
|
|
3
9
|
## 1.1.0
|
|
4
10
|
|
|
5
11
|
- Add optional Impeccable design guidance in Hybrid settings and `/hybrid-impeccable redesign` or `polish`. The configured design lead authors the direction, reviewers validate it, and the implementation model follows the approved document.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
export const continuationFile=sessionID=>`continuation-${createHash('sha256').update(JSON.stringify(sessionID)).digest('hex')}.json`;
|
|
3
|
+
export function continuationNotice(notice,session) {
|
|
4
|
+
return notice?.busy===session?.time?.busy ? notice?.text??'' : '';
|
|
5
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {isReviewerSession} from './opencode-reviewer.mjs';
|
|
3
|
+
import {writeJSONAtomic,runtimePath} from '../core/state-store.mjs';
|
|
4
|
+
import {operationBusy} from '../core/operation-lock.mjs';
|
|
5
|
+
import {continuationFile} from './continuation-notice.mjs';
|
|
6
|
+
|
|
7
|
+
const SOURCE='hybrid-silent-stop';
|
|
8
|
+
const unwrap=value=>value.data??value;
|
|
9
|
+
const digest=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
10
|
+
const textOf=message=>(message?.content??[]).filter(p=>p.type==='text').map(p=>p.text??'').join('').trim();
|
|
11
|
+
const meaningful=part=>part.type==='tool' && part.state?.status==='completed' &&
|
|
12
|
+
!['hybrid_activity','todowrite','todoread'].includes(part.name) &&
|
|
13
|
+
!(part.name==='hybrid_checkpoint' && ['status','inspect'].includes(part.state.input?.action));
|
|
14
|
+
|
|
15
|
+
export const CONTINUATION_POLICY=`Finish the turn visibly. Reasoning and tool output are not a handoff to the user. While authorized work remains, continue gathering evidence, resolving findings and calling the next checkpoint; do not merely promise the next tool call and stop. When the user asked to review the plan before coding, finish planning and present the plan with its actual approval status and an explicit request for their review; do not start implementation. Respect all user pauses, cancellations, scope and approval requirements. If truly blocked by missing user input, authentication or unavailable external evidence, explain what is unfinished, the exact blocker and the specific next action. A model stop or successful turn is not task completion; claim completion only when the milestone and checkpoint are complete.`;
|
|
16
|
+
|
|
17
|
+
// No provider call or semantic guess about consent: visible final answers are
|
|
18
|
+
// left alone. This guard repairs the observed reasoning-only terminal response.
|
|
19
|
+
// Receipts live in the host transcript, not the approval/state files.
|
|
20
|
+
export function silentStopDecision(messages,state,sessionID,background=[]) {
|
|
21
|
+
if(!state || background.some(work=>work.sessionID===sessionID))return null;
|
|
22
|
+
const userIndex=messages.findLastIndex(m=>m.type==='user');
|
|
23
|
+
if(userIndex<0)return null;
|
|
24
|
+
const turn=messages.slice(userIndex+1),last=turn.findLast(m=>m.type==='assistant');
|
|
25
|
+
if(!last || last.error || last.finish!=='stop' || textOf(last))return null;
|
|
26
|
+
if(turn.some(m=>m.type==='assistant' && (m.error || ['error','length','content-filter'].includes(m.finish))))return null;
|
|
27
|
+
// Only sessions actually participating in the workflow, never an unrelated
|
|
28
|
+
// chat in the same directory or a model-generated reviewer session.
|
|
29
|
+
if(!messages.some(m=>m.type==='assistant' && (m.content??[]).some(p=>
|
|
30
|
+
p.name==='hybrid_checkpoint' && p.state?.status==='completed' &&
|
|
31
|
+
['begin','plan','replan','design','review','verify','browser','fix','finish'].includes(p.state.input?.action))))return null;
|
|
32
|
+
const progress=[...new Set(turn.flatMap(m=>m.type==='assistant'?(m.content??[]):[])
|
|
33
|
+
.filter(meaningful).map(p=>digest([p.name,p.state.input,p.state.content])))].sort();
|
|
34
|
+
const key=digest([state.id,state.phase,state.candidate?.hash,state.approval?.artifactHash,
|
|
35
|
+
state.designApproval?.artifactHash,state.checkpoint,progress]);
|
|
36
|
+
const receipts=turn.filter(m=>m.type==='synthetic' && m.metadata?.source===SOURCE && m.metadata?.taskID===state.id);
|
|
37
|
+
if(receipts.some(m=>m.metadata?.key===key))return {
|
|
38
|
+
notice:`The agent stopped without a user-facing handoff after recovery. Checkpoint: ${state.phase}. Send a message to resume. Existing approvals are unchanged.`,
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
resume:false,metadata:{source:SOURCE,taskID:state.id,key},
|
|
42
|
+
description:'Hybrid Workflow: recovering a silent stop; the agent has not handed the task back to you.',
|
|
43
|
+
text:`Hybrid Workflow detected a terminal response with no user-facing answer. Checkpoint phase: ${state.phase}. ${CONTINUATION_POLICY} Continue the next authorized step now, or provide the clear final handoff if waiting for the user. This recovery is not user consent, a new task, or permission to bypass a checkpoint. Do not repeat an unchanged failing call.`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function registerContinuation(ctx,forSession) {
|
|
48
|
+
// experimental.idle was verified against the 2.2.5 API/host. Older hosts keep
|
|
49
|
+
// the instructions but cannot safely emulate this using after-idle timers.
|
|
50
|
+
const [major,minor,patch]=String(ctx.app?.version??'').split('.').map(Number);
|
|
51
|
+
if(major!==2 || minor<2 || (minor===2 && patch<5) || typeof ctx.session.synthetic!=='function')return;
|
|
52
|
+
const pending=new Set();
|
|
53
|
+
const generation=new Map();
|
|
54
|
+
await ctx.session.hook('prompt',event=>{generation.set(event.sessionID,(generation.get(event.sessionID)??0)+1);});
|
|
55
|
+
await ctx.session.hook('experimental.idle',async event=>{
|
|
56
|
+
if(pending.has(event.sessionID))return;
|
|
57
|
+
pending.add(event.sessionID);
|
|
58
|
+
try {
|
|
59
|
+
if(await isReviewerSession(ctx,event.sessionID))return;
|
|
60
|
+
const session=unwrap(await ctx.session.get({sessionID:event.sessionID}));
|
|
61
|
+
if(session.parentID || session.time?.archived)return;
|
|
62
|
+
const epoch=generation.get(event.sessionID)??0;
|
|
63
|
+
const controller=await forSession(event.sessionID);
|
|
64
|
+
if(operationBusy(controller.lockFile))return;
|
|
65
|
+
const messages=unwrap(await ctx.session.context({sessionID:event.sessionID}));
|
|
66
|
+
const decision=silentStopDecision(messages,controller.read(),event.sessionID,event.background);
|
|
67
|
+
if(!decision)return;
|
|
68
|
+
const current=unwrap(await ctx.session.get({sessionID:event.sessionID}));
|
|
69
|
+
const busy=Number(new Date(current.time?.busy??0)),idle=Number(new Date(current.time?.idle??0));
|
|
70
|
+
if(!busy || idle>=busy || current.time?.busy!==session.time?.busy || current.time?.archived ||
|
|
71
|
+
(generation.get(event.sessionID)??0)!==epoch)return;
|
|
72
|
+
if(decision.notice){
|
|
73
|
+
// A synthetic notice would itself cause another model call inside this
|
|
74
|
+
// execution, even with resume:false. The sidebar reads this inert receipt.
|
|
75
|
+
writeJSONAtomic(runtimePath(controller.folder,continuationFile(event.sessionID)),{
|
|
76
|
+
sessionID:event.sessionID,taskID:controller.read()?.id,busy:current.time.busy,text:decision.notice,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Drain only within the existing execution. If a user interrupts while the
|
|
81
|
+
// async hook is finishing, resume:false cannot wake the stopped session.
|
|
82
|
+
await ctx.session.synthetic({sessionID:event.sessionID,...decision});
|
|
83
|
+
} finally {pending.delete(event.sessionID);}
|
|
84
|
+
});
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@rockysoft/opencode-hybrid-workflow",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Portable configurable-reviewer planning, design, implementation checkpoints and usage sidebar for OpenCode 2",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"author": "RockySoft",
|
|
8
|
-
"keywords": [
|
|
9
|
-
"opencode",
|
|
10
|
-
"opencode-plugin",
|
|
11
|
-
"agents",
|
|
12
|
-
"code-review",
|
|
13
|
-
"workflow"
|
|
14
|
-
],
|
|
15
|
-
"publishConfig": {
|
|
16
|
-
"access": "public",
|
|
17
|
-
"tag": "latest"
|
|
18
|
-
},
|
|
19
|
-
"exports": {
|
|
20
|
-
".": "./index.js",
|
|
21
|
-
"./tui": "./tui.js"
|
|
22
|
-
},
|
|
23
|
-
"bin": {
|
|
24
|
-
"opencode-hybrid": "./cli.mjs"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"index.js",
|
|
28
|
-
"tui.js",
|
|
29
|
-
"cli.mjs",
|
|
30
|
-
"lib/",
|
|
31
|
-
"core/",
|
|
32
|
-
"sidebar/",
|
|
33
|
-
"assets/",
|
|
34
|
-
"README.md",
|
|
35
|
-
"THIRD_PARTY_NOTICES.md",
|
|
36
|
-
"LICENSE",
|
|
37
|
-
"docs/"
|
|
38
|
-
],
|
|
39
|
-
"engines": {
|
|
40
|
-
"node": ">=22"
|
|
41
|
-
},
|
|
42
|
-
"scripts": {
|
|
43
|
-
"build": "node scripts/build.mjs",
|
|
44
|
-
"prepack": "npm run build",
|
|
45
|
-
"test": "node --test test/*.test.mjs",
|
|
46
|
-
"doctor": "node cli.mjs doctor"
|
|
47
|
-
},
|
|
48
|
-
"devDependencies": {
|
|
49
|
-
"@babel/core": "7.29.7",
|
|
50
|
-
"@babel/preset-typescript": "7.27.1",
|
|
51
|
-
"babel-preset-solid": "1.9.12"
|
|
52
|
-
},
|
|
53
|
-
"peerDependencies": {
|
|
54
|
-
"@opencode/plugin": ">=2.1.2 <3",
|
|
55
|
-
"@opentui/core": ">=0.6.6",
|
|
56
|
-
"@opentui/solid": ">=0.6.6",
|
|
57
|
-
"solid-js": ">=1.9.0"
|
|
58
|
-
},
|
|
59
|
-
"peerDependenciesMeta": {
|
|
60
|
-
"@opencode/plugin": {
|
|
61
|
-
"optional": true
|
|
62
|
-
},
|
|
63
|
-
"@opentui/core": {
|
|
64
|
-
"optional": true
|
|
65
|
-
},
|
|
66
|
-
"@opentui/solid": {
|
|
67
|
-
"optional": true
|
|
68
|
-
},
|
|
69
|
-
"solid-js": {
|
|
70
|
-
"optional": true
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"dependencies": {
|
|
74
|
-
"diff": "9.0.0"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@rockysoft/opencode-hybrid-workflow",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Portable configurable-reviewer planning, design, implementation checkpoints and usage sidebar for OpenCode 2",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "RockySoft",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"opencode",
|
|
10
|
+
"opencode-plugin",
|
|
11
|
+
"agents",
|
|
12
|
+
"code-review",
|
|
13
|
+
"workflow"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"tag": "latest"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./index.js",
|
|
21
|
+
"./tui": "./tui.js"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"opencode-hybrid": "./cli.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"tui.js",
|
|
29
|
+
"cli.mjs",
|
|
30
|
+
"lib/",
|
|
31
|
+
"core/",
|
|
32
|
+
"sidebar/",
|
|
33
|
+
"assets/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"THIRD_PARTY_NOTICES.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"docs/"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "node scripts/build.mjs",
|
|
44
|
+
"prepack": "npm run build",
|
|
45
|
+
"test": "node --test test/*.test.mjs",
|
|
46
|
+
"doctor": "node cli.mjs doctor"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@babel/core": "7.29.7",
|
|
50
|
+
"@babel/preset-typescript": "7.27.1",
|
|
51
|
+
"babel-preset-solid": "1.9.12"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@opencode/plugin": ">=2.1.2 <3",
|
|
55
|
+
"@opentui/core": ">=0.6.6",
|
|
56
|
+
"@opentui/solid": ">=0.6.6",
|
|
57
|
+
"solid-js": ">=1.9.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@opencode/plugin": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@opentui/core": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@opentui/solid": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"solid-js": {
|
|
70
|
+
"optional": true
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"diff": "9.0.0"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/sidebar/reader.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { reportedUsage, priceCall } from '../core/pricing.mjs';
|
|
5
5
|
import { summarize, countedCalls } from '../core/dashboard-state.js';
|
|
6
6
|
import { runtimeFor } from '../lib/config.mjs';
|
|
7
|
+
import {continuationFile} from '../lib/continuation-notice.mjs';
|
|
7
8
|
|
|
8
9
|
export const projectKey = directory => createHash('sha256').update(process.platform==='win32'?directory.toLowerCase():directory).digest('hex').slice(0,24);
|
|
9
10
|
export const number = value => value == null ? 'Unavailable' : value.toLocaleString('en-US');
|
|
@@ -42,7 +43,7 @@ export function createReader(settings) {
|
|
|
42
43
|
if (relative === '..' || relative.startsWith('..'+path.sep) || path.isAbsolute(relative)) throw new Error('Telemetry path outside runtime');
|
|
43
44
|
return resolved;
|
|
44
45
|
}
|
|
45
|
-
return async directory => {
|
|
46
|
+
return async (directory,sessionID) => {
|
|
46
47
|
if (typeof directory !== 'string' || !path.isAbsolute(directory)) throw new Error('Project location unavailable');
|
|
47
48
|
const project = await realpath(directory);
|
|
48
49
|
const efforts = settings.efforts;
|
|
@@ -57,6 +58,10 @@ export function createReader(settings) {
|
|
|
57
58
|
const normalize=p=>process.platform==='win32'?p.toLowerCase():p;
|
|
58
59
|
if (!state.baseline?.root || normalize(await realpath(state.baseline.root)) !== normalize(project)) throw new Error('Project telemetry does not match current project');
|
|
59
60
|
const result = { ...empty, task: state.id, key: `${empty.key}:${state.id}` };
|
|
61
|
+
if(typeof sessionID==='string')try {
|
|
62
|
+
const notice=await json(await contained(runtime,path.join(folder,continuationFile(sessionID))));
|
|
63
|
+
if(notice.sessionID===sessionID&¬ice.taskID===state.id&&typeof notice.text==='string')result.continuation=notice;
|
|
64
|
+
}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
60
65
|
let taskFolder;
|
|
61
66
|
try { taskFolder = await contained(runtime, path.join(folder, state.id)); }
|
|
62
67
|
catch (error) { if (error.code === 'ENOENT') return result; throw error; }
|
package/tui.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createReader, cards, number, money } from './sidebar/reader.mjs';
|
|
|
14
14
|
import { captureTotals } from './core/dashboard-state.js';
|
|
15
15
|
import { createSettingsUI } from './sidebar/settings.mjs';
|
|
16
16
|
import { legacyTotals } from './sidebar/legacy-totals.mjs';
|
|
17
|
+
import { continuationNotice } from './lib/continuation-notice.mjs';
|
|
17
18
|
export default Plugin.define({
|
|
18
19
|
id: 'Hybrid-Workflow',
|
|
19
20
|
setup(context) {
|
|
@@ -81,7 +82,7 @@ export default Plugin.define({
|
|
|
81
82
|
if (busy || !directory) return;
|
|
82
83
|
busy = true;
|
|
83
84
|
try {
|
|
84
|
-
const next = await read(directory);
|
|
85
|
+
const next = await read(directory, props.sessionID);
|
|
85
86
|
if (!disposed) {
|
|
86
87
|
setSnapshot(next);
|
|
87
88
|
setError('');
|
|
@@ -103,6 +104,10 @@ export default Plugin.define({
|
|
|
103
104
|
});
|
|
104
105
|
});
|
|
105
106
|
const totals = createMemo(() => snapshot() ? cards(snapshot(), saved.resets[snapshot().key]) : []);
|
|
107
|
+
const continuation = createMemo(() => {
|
|
108
|
+
const notice = snapshot()?.continuation;
|
|
109
|
+
return continuationNotice(notice, context.data.session.get(props.sessionID));
|
|
110
|
+
});
|
|
106
111
|
async function clear() {
|
|
107
112
|
const value = snapshot();
|
|
108
113
|
if (!value) return;
|
|
@@ -170,61 +175,72 @@ export default Plugin.define({
|
|
|
170
175
|
return _el$6;
|
|
171
176
|
}
|
|
172
177
|
}), null);
|
|
178
|
+
_$insert(_el$3, _$createComponent(Show, {
|
|
179
|
+
get when() {
|
|
180
|
+
return continuation();
|
|
181
|
+
},
|
|
182
|
+
get children() {
|
|
183
|
+
var _el$7 = _$createElement("text");
|
|
184
|
+
_$insert(_el$7, continuation);
|
|
185
|
+
_$effect(_$p => _$setProp(_el$7, "fg", context.theme.text.base, _$p));
|
|
186
|
+
return _el$7;
|
|
187
|
+
}
|
|
188
|
+
}), null);
|
|
173
189
|
_$insert(_el$3, _$createComponent(For, {
|
|
174
190
|
get each() {
|
|
175
191
|
return totals();
|
|
176
192
|
},
|
|
177
193
|
children: card => (() => {
|
|
178
|
-
var _el$
|
|
179
|
-
_el$
|
|
180
|
-
_el$
|
|
181
|
-
_el$
|
|
182
|
-
_el$
|
|
183
|
-
_el$
|
|
184
|
-
_el$17 = _$createElement("text"),
|
|
194
|
+
var _el$12 = _$createElement("box"),
|
|
195
|
+
_el$13 = _$createElement("text"),
|
|
196
|
+
_el$14 = _$createElement("b"),
|
|
197
|
+
_el$15 = _$createTextNode(` · `),
|
|
198
|
+
_el$16 = _$createElement("text"),
|
|
199
|
+
_el$17 = _$createTextNode(` tokens · `),
|
|
185
200
|
_el$18 = _$createElement("text"),
|
|
186
|
-
_el$19 = _$
|
|
187
|
-
_el$20 = _$
|
|
188
|
-
_el$21 = _$
|
|
189
|
-
_el$22 = _$
|
|
190
|
-
_el$23 = _$
|
|
191
|
-
|
|
192
|
-
_$insertNode(_el$11, _el$15);
|
|
193
|
-
_$insertNode(_el$11, _el$17);
|
|
194
|
-
_$insertNode(_el$11, _el$18);
|
|
195
|
-
_$insertNode(_el$11, _el$20);
|
|
196
|
-
_$insertNode(_el$11, _el$22);
|
|
197
|
-
_$setProp(_el$11, "flexDirection", "column");
|
|
201
|
+
_el$19 = _$createElement("text"),
|
|
202
|
+
_el$20 = _$createTextNode(`Input: `),
|
|
203
|
+
_el$21 = _$createElement("text"),
|
|
204
|
+
_el$22 = _$createTextNode(`Output: `),
|
|
205
|
+
_el$23 = _$createElement("text"),
|
|
206
|
+
_el$24 = _$createTextNode(`Cache: `);
|
|
198
207
|
_$insertNode(_el$12, _el$13);
|
|
208
|
+
_$insertNode(_el$12, _el$16);
|
|
209
|
+
_$insertNode(_el$12, _el$18);
|
|
210
|
+
_$insertNode(_el$12, _el$19);
|
|
211
|
+
_$insertNode(_el$12, _el$21);
|
|
212
|
+
_$insertNode(_el$12, _el$23);
|
|
213
|
+
_$setProp(_el$12, "flexDirection", "column");
|
|
199
214
|
_$insertNode(_el$13, _el$14);
|
|
200
|
-
_$
|
|
201
|
-
_$insert(_el$
|
|
202
|
-
_$
|
|
203
|
-
_$
|
|
204
|
-
_$insert(_el$
|
|
205
|
-
_$insert(_el$
|
|
206
|
-
_$
|
|
207
|
-
_$
|
|
208
|
-
_$
|
|
209
|
-
_$
|
|
210
|
-
_$
|
|
211
|
-
_$
|
|
212
|
-
_$insert(_el$
|
|
215
|
+
_$insertNode(_el$14, _el$15);
|
|
216
|
+
_$insert(_el$14, () => card.model ?? source().settings.models[card.who], _el$15);
|
|
217
|
+
_$insert(_el$14, () => card.effort, null);
|
|
218
|
+
_$insertNode(_el$16, _el$17);
|
|
219
|
+
_$insert(_el$16, () => number(card.total), _el$17);
|
|
220
|
+
_$insert(_el$16, () => card.status, null);
|
|
221
|
+
_$insert(_el$18, () => money(card.cost));
|
|
222
|
+
_$insertNode(_el$19, _el$20);
|
|
223
|
+
_$insert(_el$19, () => number(card.input), null);
|
|
224
|
+
_$insertNode(_el$21, _el$22);
|
|
225
|
+
_$insert(_el$21, () => number(card.output), null);
|
|
226
|
+
_$insertNode(_el$23, _el$24);
|
|
227
|
+
_$insert(_el$23, () => number(card.cache), null);
|
|
228
|
+
_$insert(_el$12, _$createComponent(Show, {
|
|
213
229
|
get when() {
|
|
214
230
|
return card.partial;
|
|
215
231
|
},
|
|
216
232
|
get children() {
|
|
217
|
-
var _el$
|
|
218
|
-
_el$
|
|
219
|
-
_el$
|
|
220
|
-
_el$
|
|
221
|
-
_$insertNode(_el$
|
|
222
|
-
_$insertNode(_el$
|
|
223
|
-
_$insertNode(_el$
|
|
224
|
-
_$insert(_el$
|
|
225
|
-
_$insert(_el$
|
|
226
|
-
_$effect(_$p => _$setProp(_el$
|
|
227
|
-
return _el$
|
|
233
|
+
var _el$25 = _$createElement("text"),
|
|
234
|
+
_el$26 = _$createTextNode(`Partial · `),
|
|
235
|
+
_el$27 = _$createTextNode(`/`),
|
|
236
|
+
_el$28 = _$createTextNode(` measured`);
|
|
237
|
+
_$insertNode(_el$25, _el$26);
|
|
238
|
+
_$insertNode(_el$25, _el$27);
|
|
239
|
+
_$insertNode(_el$25, _el$28);
|
|
240
|
+
_$insert(_el$25, () => card.measuredCalls, _el$27);
|
|
241
|
+
_$insert(_el$25, () => card.calls, _el$28);
|
|
242
|
+
_$effect(_$p => _$setProp(_el$25, "fg", context.theme.text.muted, _$p));
|
|
243
|
+
return _el$25;
|
|
228
244
|
}
|
|
229
245
|
}), null);
|
|
230
246
|
_$effect(_p$ => {
|
|
@@ -234,12 +250,12 @@ export default Plugin.define({
|
|
|
234
250
|
_v$4 = context.theme.text.muted,
|
|
235
251
|
_v$5 = context.theme.text.muted,
|
|
236
252
|
_v$6 = context.theme.text.muted;
|
|
237
|
-
_v$ !== _p$.e && (_p$.e = _$setProp(_el$
|
|
238
|
-
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
239
|
-
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$
|
|
240
|
-
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$
|
|
241
|
-
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$
|
|
242
|
-
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$
|
|
253
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$13, "fg", _v$, _p$.e));
|
|
254
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$16, "fg", _v$2, _p$.t));
|
|
255
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$18, "fg", _v$3, _p$.a));
|
|
256
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$19, "fg", _v$4, _p$.o));
|
|
257
|
+
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$21, "fg", _v$5, _p$.i));
|
|
258
|
+
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$23, "fg", _v$6, _p$.n));
|
|
243
259
|
return _p$;
|
|
244
260
|
}, {
|
|
245
261
|
e: undefined,
|
|
@@ -249,7 +265,7 @@ export default Plugin.define({
|
|
|
249
265
|
i: undefined,
|
|
250
266
|
n: undefined
|
|
251
267
|
});
|
|
252
|
-
return _el$
|
|
268
|
+
return _el$12;
|
|
253
269
|
})()
|
|
254
270
|
}), null);
|
|
255
271
|
_$insert(_el$3, _$createComponent(Show, {
|
|
@@ -258,32 +274,32 @@ export default Plugin.define({
|
|
|
258
274
|
},
|
|
259
275
|
get children() {
|
|
260
276
|
return [(() => {
|
|
261
|
-
var _el$
|
|
262
|
-
_$insert(_el$
|
|
263
|
-
_$effect(_$p => _$setProp(_el$
|
|
264
|
-
return _el$
|
|
277
|
+
var _el$8 = _$createElement("text");
|
|
278
|
+
_$insert(_el$8, () => snapshot().task ? 'Project bridge usage · USD' : 'No bridge calls in this project');
|
|
279
|
+
_$effect(_$p => _$setProp(_el$8, "fg", context.theme.text.muted, _$p));
|
|
280
|
+
return _el$8;
|
|
265
281
|
})(), (() => {
|
|
266
|
-
var _el$
|
|
267
|
-
_el$
|
|
268
|
-
_$insertNode(_el$
|
|
269
|
-
_$setProp(_el$
|
|
270
|
-
_$setProp(_el$
|
|
271
|
-
_$insertNode(_el$
|
|
272
|
-
_$setProp(_el$
|
|
273
|
-
_$insert(_el$
|
|
282
|
+
var _el$9 = _$createElement("box"),
|
|
283
|
+
_el$0 = _$createElement("text");
|
|
284
|
+
_$insertNode(_el$9, _el$0);
|
|
285
|
+
_$setProp(_el$9, "flexDirection", "row");
|
|
286
|
+
_$setProp(_el$9, "gap", 2);
|
|
287
|
+
_$insertNode(_el$0, _$createTextNode(`[Clear totals]`));
|
|
288
|
+
_$setProp(_el$0, "onMouseDown", clear);
|
|
289
|
+
_$insert(_el$9, _$createComponent(Show, {
|
|
274
290
|
get when() {
|
|
275
291
|
return saved.resets[snapshot()?.key];
|
|
276
292
|
},
|
|
277
293
|
get children() {
|
|
278
|
-
var _el$
|
|
279
|
-
_$insertNode(_el$
|
|
280
|
-
_$setProp(_el$
|
|
281
|
-
_$effect(_$p => _$setProp(_el$
|
|
282
|
-
return _el$
|
|
294
|
+
var _el$10 = _$createElement("text");
|
|
295
|
+
_$insertNode(_el$10, _$createTextNode(`[Restore]`));
|
|
296
|
+
_$setProp(_el$10, "onMouseDown", restore);
|
|
297
|
+
_$effect(_$p => _$setProp(_el$10, "fg", context.theme.text.muted, _$p));
|
|
298
|
+
return _el$10;
|
|
283
299
|
}
|
|
284
300
|
}), null);
|
|
285
|
-
_$effect(_$p => _$setProp(_el$
|
|
286
|
-
return _el$
|
|
301
|
+
_$effect(_$p => _$setProp(_el$0, "fg", context.theme.text.muted, _$p));
|
|
302
|
+
return _el$9;
|
|
287
303
|
})()];
|
|
288
304
|
}
|
|
289
305
|
}), null);
|