@bojackduy/opencode-loopd 1.9.5 → 1.9.6

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/server.js CHANGED
@@ -499,10 +499,12 @@ function resolveGoalCreationConfig(input) {
499
499
  const defaults = input.defaults || {};
500
500
  const explicitAgent = cleanText(requested.agent);
501
501
  const defaultAgent = cleanText(defaults.defaultAgent);
502
- const agent = explicitAgent || defaultAgent || undefined;
502
+ const parentAgent = cleanText(defaults.parentAgent);
503
+ const agent = explicitAgent || parentAgent || defaultAgent || undefined;
503
504
  const explicitModel = cleanText(requested.model);
504
505
  const defaultModel = cleanText(defaults.defaultModel);
505
- const model = explicitModel || defaultModel || undefined;
506
+ const parentModel = cleanText(defaults.parentModel);
507
+ const model = explicitModel || parentModel || defaultModel || undefined;
506
508
  if (model && !isValidModelRef(model)) {
507
509
  return {
508
510
  ok: false,
@@ -532,8 +534,8 @@ function resolveGoalCreationConfig(input) {
532
534
  checkCwd: requested.checkCwd || (workspaceWrite ? input.directory : undefined)
533
535
  },
534
536
  defaultsApplied: {
535
- agent: !explicitAgent && Boolean(defaultAgent),
536
- model: !explicitModel && Boolean(defaultModel),
537
+ agent: !explicitAgent && Boolean(parentAgent || defaultAgent),
538
+ model: !explicitModel && Boolean(parentModel || defaultModel),
537
539
  checks: explicitChecks.length === 0 && defaultChecks.length > 0
538
540
  }
539
541
  };
@@ -1975,6 +1977,30 @@ function createRealHost(client, directory) {
1975
1977
  await logServerEvent(directory, "worker.prompted", { sessionID });
1976
1978
  return { messageID: result?.data?.messageID };
1977
1979
  },
1980
+ async readSession(sessionID) {
1981
+ try {
1982
+ const result = await client.session.get({ path: { id: sessionID } });
1983
+ if (result?.error)
1984
+ return;
1985
+ const data = result?.data;
1986
+ if (!data || typeof data !== "object")
1987
+ return;
1988
+ const agent = typeof data.agent === "string" ? data.agent : undefined;
1989
+ const rawModel = data.model;
1990
+ let model;
1991
+ if (rawModel && typeof rawModel === "object") {
1992
+ const modelID = typeof rawModel.modelID === "string" ? rawModel.modelID : typeof rawModel.id === "string" ? rawModel.id : undefined;
1993
+ const providerID = typeof rawModel.providerID === "string" ? rawModel.providerID : undefined;
1994
+ if (modelID && providerID)
1995
+ model = { providerID, modelID };
1996
+ }
1997
+ if (!agent && !model)
1998
+ return;
1999
+ return { agent, model };
2000
+ } catch {
2001
+ return;
2002
+ }
2003
+ },
1978
2004
  async sessionStatus(sessionID) {
1979
2005
  try {
1980
2006
  const result = await client.session.status({});
@@ -2349,6 +2375,10 @@ function createGoalService(host) {
2349
2375
  });
2350
2376
  if (typeof input.costBudget === "number")
2351
2377
  goal.costBudget = input.costBudget;
2378
+ if (input.parentAgent)
2379
+ goal.parentAgent = input.parentAgent;
2380
+ if (input.parentModel)
2381
+ goal.parentModel = input.parentModel;
2352
2382
  const artifactDir = goalArtifactDir(directory, id);
2353
2383
  goal.config.artifactDir = artifactDir;
2354
2384
  if (!goal.config.progressFile)
@@ -3125,15 +3155,15 @@ function appendVerificationAttempt(recent, attempt) {
3125
3155
  import { exec as execChild } from "child_process";
3126
3156
  import { promisify } from "util";
3127
3157
  var execAsync = promisify(execChild);
3128
- function goalTools(dir, goalService, hostSessionID, defaults = {}) {
3158
+ function goalTools(dir, goalService, hostSessionID, defaults = {}, host) {
3129
3159
  return {
3130
3160
  loopd_create_goal: tool({
3131
- description: "Create a new background loop goal (contract: objective + checks + agent/model + workspaceWrite). " + "The engine spawns a dedicated worker session that does the work autonomously \u2014 it never runs in this chat. " + "Call this after clarifying the contract with the user. " + "Worker identity is free-form: agent is any OpenCode agent name (built-in, ~/.config/opencode/agents/*.md, or opencode.jsonc agent.* \u2014 discover with `opencode agent list`), " + 'model is any "providerID/modelID" (discover with `opencode models [provider]`). Both are sent on every worker prompt. ' + "Host is the acceptance authority: checks must pass for complete_goal (free retry if rejected <3, blocked after 3). " + "Workspace-writing goals are serialized (only one active writer) and require checks. " + "agent/model are optional \u2014 fall back to the parent session's agent/model, or plugin defaultAgent/defaultModel in opencode.jsonc.",
3161
+ description: "Create a new background loop goal (contract: objective + checks + agent/model + workspaceWrite). " + "The engine spawns a dedicated worker session that does the work autonomously \u2014 it never runs in this chat. " + "Call this after clarifying the contract with the user. " + "Worker identity is free-form: agent is any OpenCode agent name (built-in, ~/.config/opencode/agents/*.md, or opencode.jsonc agent.* \u2014 discover with `opencode agent list`), " + 'model is any "providerID/modelID" (discover with `opencode models [provider]`). When omitted, both inherit the CALLING session\'s live agent/model (read at creation), then plugin defaultAgent/defaultModel. ' + "Host is the acceptance authority: checks must pass for complete_goal (free retry if rejected <3, blocked after 3). " + "Workspace-writing goals are serialized (only one active writer) and require checks.",
3132
3162
  args: {
3133
3163
  name: tool.schema.string().describe("Short goal name (used in the dashboard)."),
3134
3164
  objective: tool.schema.string().describe("What the goal should accomplish, in detail."),
3135
- agent: tool.schema.string().optional().describe(`Agent to run the worker as (e.g. "researcher", "smart-agent"). Optional \u2014 uses parent session's agent if omitted, or plugin defaultAgent.`),
3136
- model: tool.schema.string().optional().describe(`Model to run the worker as, as "providerID/modelID" (e.g. "openai/gpt-5.6-sol", "ollama/qwen3.8:27b"). Optional \u2014 uses parent session's model if omitted, or plugin defaultModel.`),
3165
+ agent: tool.schema.string().optional().describe(`Agent to run the worker as (e.g. "researcher", "smart-agent"). Optional \u2014 inherits the calling session's agent if omitted, else plugin defaultAgent.`),
3166
+ model: tool.schema.string().optional().describe(`Model to run the worker as, as "providerID/modelID" (e.g. "openai/gpt-5.6-sol", "ollama/qwen3.8:27b"). Optional \u2014 inherits the calling session's live model if omitted, else plugin defaultModel.`),
3137
3167
  costBudget: tool.schema.number().optional().describe("Max provider cost in dollars before the engine stops the goal as budget_limited (e.g. 0.5). Optional \u2014 unlimited if omitted."),
3138
3168
  checks: tool.schema.array(tool.schema.string()).optional().describe('Shell commands that must pass for completion to be accepted. E.g. ["npm test"].'),
3139
3169
  checkCwd: tool.schema.string().optional().describe("Directory where completion checks run. Workspace-writing goals default to the project root."),
@@ -3219,7 +3249,7 @@ function goalTools(dir, goalService, hostSessionID, defaults = {}) {
3219
3249
  directory: dir,
3220
3250
  objective: args.objective,
3221
3251
  config,
3222
- defaults
3252
+ defaults: await withParentIdentity(defaults, host, sessionID)
3223
3253
  });
3224
3254
  if (!resolution.ok) {
3225
3255
  return {
@@ -3232,12 +3262,15 @@ function goalTools(dir, goalService, hostSessionID, defaults = {}) {
3232
3262
  };
3233
3263
  }
3234
3264
  try {
3265
+ const parentDefaults = await withParentIdentity(defaults, host, sessionID);
3235
3266
  const { goal, worker } = await goalService.start(dir, {
3236
3267
  name: args.name,
3237
3268
  objective: args.objective,
3238
3269
  ownerSessionID: sessionID,
3239
3270
  config: resolution.config,
3240
- costBudget
3271
+ costBudget,
3272
+ parentAgent: parentDefaults.parentAgent,
3273
+ parentModel: parentDefaults.parentModel
3241
3274
  });
3242
3275
  return {
3243
3276
  title: "Goal created",
@@ -3595,6 +3628,34 @@ ${failureDetails.slice(0, 500)}`,
3595
3628
  })
3596
3629
  };
3597
3630
  }
3631
+ var parentIdentityCache = new Map;
3632
+ async function withParentIdentity(defaults, host, ownerSessionID) {
3633
+ if (!host?.readSession)
3634
+ return defaults;
3635
+ let cached = parentIdentityCache.get(ownerSessionID);
3636
+ if (!cached) {
3637
+ try {
3638
+ const identity = await host.readSession(ownerSessionID);
3639
+ cached = {
3640
+ agent: identity?.agent,
3641
+ model: identity?.model ? `${identity.model.providerID}/${identity.model.modelID}` : undefined
3642
+ };
3643
+ } catch {
3644
+ cached = {};
3645
+ }
3646
+ parentIdentityCache.set(ownerSessionID, cached);
3647
+ if (parentIdentityCache.size > 200) {
3648
+ const first = parentIdentityCache.keys().next();
3649
+ if (!first.done)
3650
+ parentIdentityCache.delete(first.value);
3651
+ }
3652
+ }
3653
+ return {
3654
+ ...defaults,
3655
+ parentAgent: cached.agent,
3656
+ parentModel: cached.model
3657
+ };
3658
+ }
3598
3659
  function findGoalByWorkerSession(state, sessionID) {
3599
3660
  if (!sessionID)
3600
3661
  return;
@@ -3809,6 +3870,8 @@ function ownerTools(options) {
3809
3870
  workspaceWrite: goal.config.workspaceWrite,
3810
3871
  agent: goal.config.agent,
3811
3872
  model: goal.config.model,
3873
+ parentAgent: goal.parentAgent,
3874
+ parentModel: goal.parentModel,
3812
3875
  schedule: goal.config.schedule
3813
3876
  },
3814
3877
  lastProgress: goal.lastProgress,
@@ -4296,7 +4359,7 @@ var server = async ({ client, directory }, pluginOptions) => {
4296
4359
  if (type?.startsWith("session."))
4297
4360
  reconcileInBackground();
4298
4361
  },
4299
- tool: { ...goalTools(directory, goalService, undefined, defaults), ...ownerTools({ directory, host, goalService }) },
4362
+ tool: { ...goalTools(directory, goalService, undefined, defaults, host), ...ownerTools({ directory, host, goalService }) },
4300
4363
  "tool.execute.before": async (input, _output) => {
4301
4364
  const activeWorkers = goalService.getActiveWorkers();
4302
4365
  let matchedGoalID;
package/dist/tui.js CHANGED
@@ -1736,7 +1736,7 @@ function LoopDashboard(props) {
1736
1736
  bold: true
1737
1737
  }, _$p));
1738
1738
  return _el$145;
1739
- })(), agentName ? [(() => {
1739
+ })(), _$memo(() => agentName ? [(() => {
1740
1740
  var _el$161 = _$createElement("span");
1741
1741
  _$insert(_el$161, agentName);
1742
1742
  _$effect((_$p) => _$setProp(_el$161, "style", {
@@ -1753,14 +1753,36 @@ function LoopDashboard(props) {
1753
1753
  fg: theme().textMuted
1754
1754
  }, _$p));
1755
1755
  return _el$162;
1756
- })())] : (() => {
1756
+ })())] : goal().parentAgent ? [(() => {
1757
1757
  var _el$165 = _$createElement("span");
1758
- _$insertNode(_el$165, _$createTextNode(`parent`));
1758
+ _$insertNode(_el$165, _$createTextNode(`\u21A9 `));
1759
1759
  _$effect((_$p) => _$setProp(_el$165, "style", {
1760
1760
  fg: theme().textMuted
1761
1761
  }, _$p));
1762
1762
  return _el$165;
1763
1763
  })(), (() => {
1764
+ var _el$167 = _$createElement("span");
1765
+ _$insert(_el$167, () => goal().parentAgent);
1766
+ _$effect((_$p) => _$setProp(_el$167, "style", {
1767
+ fg: theme().text,
1768
+ bold: true
1769
+ }, _$p));
1770
+ return _el$167;
1771
+ })(), (() => {
1772
+ var _el$168 = _$createElement("span");
1773
+ _$insertNode(_el$168, _$createTextNode(` (parent)`));
1774
+ _$effect((_$p) => _$setProp(_el$168, "style", {
1775
+ fg: theme().textMuted
1776
+ }, _$p));
1777
+ return _el$168;
1778
+ })()] : (() => {
1779
+ var _el$170 = _$createElement("span");
1780
+ _$insertNode(_el$170, _$createTextNode(`parent`));
1781
+ _$effect((_$p) => _$setProp(_el$170, "style", {
1782
+ fg: theme().textMuted
1783
+ }, _$p));
1784
+ return _el$170;
1785
+ })()), (() => {
1764
1786
  var _el$147 = _$createElement("span");
1765
1787
  _$insertNode(_el$147, _$createTextNode(` \u2502 \uD83E\uDDE0 `));
1766
1788
  _$effect((_$p) => _$setProp(_el$147, "style", {
@@ -1775,30 +1797,52 @@ function LoopDashboard(props) {
1775
1797
  bold: true
1776
1798
  }, _$p));
1777
1799
  return _el$149;
1778
- })(), model && slash > 0 ? [(() => {
1779
- var _el$167 = _$createElement("span"), _el$168 = _$createTextNode(`/`);
1780
- _$insertNode(_el$167, _el$168);
1781
- _$insert(_el$167, () => model.slice(0, slash), _el$168);
1782
- _$effect((_$p) => _$setProp(_el$167, "style", {
1800
+ })(), _$memo(() => model && slash > 0 ? [(() => {
1801
+ var _el$172 = _$createElement("span"), _el$173 = _$createTextNode(`/`);
1802
+ _$insertNode(_el$172, _el$173);
1803
+ _$insert(_el$172, () => model.slice(0, slash), _el$173);
1804
+ _$effect((_$p) => _$setProp(_el$172, "style", {
1783
1805
  fg: theme().textMuted
1784
1806
  }, _$p));
1785
- return _el$167;
1807
+ return _el$172;
1786
1808
  })(), (() => {
1787
- var _el$169 = _$createElement("span");
1788
- _$insert(_el$169, () => model.slice(slash + 1));
1789
- _$effect((_$p) => _$setProp(_el$169, "style", {
1809
+ var _el$174 = _$createElement("span");
1810
+ _$insert(_el$174, () => model.slice(slash + 1));
1811
+ _$effect((_$p) => _$setProp(_el$174, "style", {
1790
1812
  fg: theme().info,
1791
1813
  bold: true
1792
1814
  }, _$p));
1793
- return _el$169;
1794
- })()] : (() => {
1795
- var _el$170 = _$createElement("span");
1796
- _$insertNode(_el$170, _$createTextNode(`parent`));
1797
- _$effect((_$p) => _$setProp(_el$170, "style", {
1815
+ return _el$174;
1816
+ })()] : goal().parentModel ? [(() => {
1817
+ var _el$175 = _$createElement("span");
1818
+ _$insertNode(_el$175, _$createTextNode(`\u21A9 `));
1819
+ _$effect((_$p) => _$setProp(_el$175, "style", {
1798
1820
  fg: theme().textMuted
1799
1821
  }, _$p));
1800
- return _el$170;
1822
+ return _el$175;
1801
1823
  })(), (() => {
1824
+ var _el$177 = _$createElement("span");
1825
+ _$insert(_el$177, () => goal().parentModel);
1826
+ _$effect((_$p) => _$setProp(_el$177, "style", {
1827
+ fg: theme().info,
1828
+ bold: true
1829
+ }, _$p));
1830
+ return _el$177;
1831
+ })(), (() => {
1832
+ var _el$178 = _$createElement("span");
1833
+ _$insertNode(_el$178, _$createTextNode(` (parent)`));
1834
+ _$effect((_$p) => _$setProp(_el$178, "style", {
1835
+ fg: theme().textMuted
1836
+ }, _$p));
1837
+ return _el$178;
1838
+ })()] : (() => {
1839
+ var _el$180 = _$createElement("span");
1840
+ _$insertNode(_el$180, _$createTextNode(`parent`));
1841
+ _$effect((_$p) => _$setProp(_el$180, "style", {
1842
+ fg: theme().textMuted
1843
+ }, _$p));
1844
+ return _el$180;
1845
+ })()), (() => {
1802
1846
  var _el$151 = _$createElement("span");
1803
1847
  _$insertNode(_el$151, _$createTextNode(` \u2502 \uD83D\uDCB0 `));
1804
1848
  _$effect((_$p) => _$setProp(_el$151, "style", {
@@ -1851,240 +1895,240 @@ function LoopDashboard(props) {
1851
1895
  _$insert(_el$122, (() => {
1852
1896
  var _c$12 = _$memo(() => !!lp());
1853
1897
  return () => _c$12() && [(() => {
1854
- var _el$172 = _$createElement("span"), _el$173 = _$createTextNode(`
1898
+ var _el$182 = _$createElement("span"), _el$183 = _$createTextNode(`
1855
1899
  \u2714 `);
1856
- _$insertNode(_el$172, _el$173);
1857
- _$effect((_$p) => _$setProp(_el$172, "style", {
1900
+ _$insertNode(_el$182, _el$183);
1901
+ _$effect((_$p) => _$setProp(_el$182, "style", {
1858
1902
  fg: theme().success
1859
1903
  }, _$p));
1860
- return _el$172;
1904
+ return _el$182;
1861
1905
  })(), (() => {
1862
- var _el$175 = _$createElement("span");
1863
- _$insertNode(_el$175, _$createTextNode(`Progress: `));
1864
- _$effect((_$p) => _$setProp(_el$175, "style", {
1906
+ var _el$185 = _$createElement("span");
1907
+ _$insertNode(_el$185, _$createTextNode(`Progress: `));
1908
+ _$effect((_$p) => _$setProp(_el$185, "style", {
1865
1909
  fg: theme().success,
1866
1910
  bold: true
1867
1911
  }, _$p));
1868
- return _el$175;
1912
+ return _el$185;
1869
1913
  })(), (() => {
1870
- var _el$177 = _$createElement("span");
1871
- _$insert(_el$177, () => lp().summary.slice(0, 100));
1872
- _$effect((_$p) => _$setProp(_el$177, "style", {
1914
+ var _el$187 = _$createElement("span");
1915
+ _$insert(_el$187, () => lp().summary.slice(0, 100));
1916
+ _$effect((_$p) => _$setProp(_el$187, "style", {
1873
1917
  fg: theme().text
1874
1918
  }, _$p));
1875
- return _el$177;
1919
+ return _el$187;
1876
1920
  })(), (() => {
1877
- var _el$178 = _$createElement("span"), _el$179 = _$createTextNode(` \u2192 `);
1878
- _$insertNode(_el$178, _el$179);
1879
- _$insert(_el$178, () => lp().next?.slice(0, 60) || "", null);
1880
- _$effect((_$p) => _$setProp(_el$178, "style", {
1921
+ var _el$188 = _$createElement("span"), _el$189 = _$createTextNode(` \u2192 `);
1922
+ _$insertNode(_el$188, _el$189);
1923
+ _$insert(_el$188, () => lp().next?.slice(0, 60) || "", null);
1924
+ _$effect((_$p) => _$setProp(_el$188, "style", {
1881
1925
  fg: theme().textMuted
1882
1926
  }, _$p));
1883
- return _el$178;
1927
+ return _el$188;
1884
1928
  })()];
1885
1929
  })(), null);
1886
1930
  _$insert(_el$122, (() => {
1887
1931
  var _c$13 = _$memo(() => !!blk());
1888
1932
  return () => _c$13() && [(() => {
1889
- var _el$180 = _$createElement("span"), _el$181 = _$createTextNode(`
1933
+ var _el$190 = _$createElement("span"), _el$191 = _$createTextNode(`
1890
1934
  \u2716 Blocked: `);
1891
- _$insertNode(_el$180, _el$181);
1892
- _$effect((_$p) => _$setProp(_el$180, "style", {
1935
+ _$insertNode(_el$190, _el$191);
1936
+ _$effect((_$p) => _$setProp(_el$190, "style", {
1893
1937
  fg: theme().error,
1894
1938
  bold: true
1895
1939
  }, _$p));
1896
- return _el$180;
1940
+ return _el$190;
1897
1941
  })(), (() => {
1898
- var _el$183 = _$createElement("span");
1899
- _$insert(_el$183, () => blk().reason.slice(0, 140));
1900
- _$effect((_$p) => _$setProp(_el$183, "style", {
1942
+ var _el$193 = _$createElement("span");
1943
+ _$insert(_el$193, () => blk().reason.slice(0, 140));
1944
+ _$effect((_$p) => _$setProp(_el$193, "style", {
1901
1945
  fg: theme().error
1902
1946
  }, _$p));
1903
- return _el$183;
1947
+ return _el$193;
1904
1948
  })(), (() => {
1905
- var _el$184 = _$createElement("span"), _el$185 = _$createTextNode(` \u2014 `);
1906
- _$insertNode(_el$184, _el$185);
1907
- _$insert(_el$184, () => blk().needed.slice(0, 60), null);
1908
- _$effect((_$p) => _$setProp(_el$184, "style", {
1949
+ var _el$194 = _$createElement("span"), _el$195 = _$createTextNode(` \u2014 `);
1950
+ _$insertNode(_el$194, _el$195);
1951
+ _$insert(_el$194, () => blk().needed.slice(0, 60), null);
1952
+ _$effect((_$p) => _$setProp(_el$194, "style", {
1909
1953
  fg: theme().textMuted
1910
1954
  }, _$p));
1911
- return _el$184;
1955
+ return _el$194;
1912
1956
  })()];
1913
1957
  })(), null);
1914
1958
  _$insert(_el$122, (() => {
1915
1959
  var _c$14 = _$memo(() => !!goal().config.artifactDir);
1916
1960
  return () => _c$14() && [(() => {
1917
- var _el$186 = _$createElement("span"), _el$187 = _$createTextNode(`
1961
+ var _el$196 = _$createElement("span"), _el$197 = _$createTextNode(`
1918
1962
  \uD83D\uDCC1 `);
1919
- _$insertNode(_el$186, _el$187);
1920
- _$effect((_$p) => _$setProp(_el$186, "style", {
1963
+ _$insertNode(_el$196, _el$197);
1964
+ _$effect((_$p) => _$setProp(_el$196, "style", {
1921
1965
  fg: theme().accent
1922
1966
  }, _$p));
1923
- return _el$186;
1967
+ return _el$196;
1924
1968
  })(), (() => {
1925
- var _el$189 = _$createElement("span");
1926
- _$insertNode(_el$189, _$createTextNode(`Artifacts: `));
1927
- _$effect((_$p) => _$setProp(_el$189, "style", {
1969
+ var _el$199 = _$createElement("span");
1970
+ _$insertNode(_el$199, _$createTextNode(`Artifacts: `));
1971
+ _$effect((_$p) => _$setProp(_el$199, "style", {
1928
1972
  fg: theme().accent,
1929
1973
  bold: true
1930
1974
  }, _$p));
1931
- return _el$189;
1975
+ return _el$199;
1932
1976
  })(), (() => {
1933
- var _el$191 = _$createElement("span");
1934
- _$insert(_el$191, () => String(goal().config.artifactDir).replace(String(props.directory), "."));
1935
- _$effect((_$p) => _$setProp(_el$191, "style", {
1977
+ var _el$201 = _$createElement("span");
1978
+ _$insert(_el$201, () => String(goal().config.artifactDir).replace(String(props.directory), "."));
1979
+ _$effect((_$p) => _$setProp(_el$201, "style", {
1936
1980
  fg: theme().textMuted
1937
1981
  }, _$p));
1938
- return _el$191;
1982
+ return _el$201;
1939
1983
  })()];
1940
1984
  })(), null);
1941
1985
  _$insert(_el$122, (() => {
1942
1986
  var _c$15 = _$memo(() => (goal().config.checks?.length ?? 0) > 0);
1943
1987
  return () => _c$15() ? [(() => {
1944
- var _el$192 = _$createElement("span"), _el$193 = _$createTextNode(`
1988
+ var _el$202 = _$createElement("span"), _el$203 = _$createTextNode(`
1945
1989
  \u25A3 `);
1946
- _$insertNode(_el$192, _el$193);
1947
- _$effect((_$p) => _$setProp(_el$192, "style", {
1990
+ _$insertNode(_el$202, _el$203);
1991
+ _$effect((_$p) => _$setProp(_el$202, "style", {
1948
1992
  fg: theme().warning
1949
1993
  }, _$p));
1950
- return _el$192;
1994
+ return _el$202;
1951
1995
  })(), (() => {
1952
- var _el$195 = _$createElement("span");
1953
- _$insertNode(_el$195, _$createTextNode(`Checks: `));
1954
- _$effect((_$p) => _$setProp(_el$195, "style", {
1996
+ var _el$205 = _$createElement("span");
1997
+ _$insertNode(_el$205, _$createTextNode(`Checks: `));
1998
+ _$effect((_$p) => _$setProp(_el$205, "style", {
1955
1999
  fg: theme().warning,
1956
2000
  bold: true
1957
2001
  }, _$p));
1958
- return _el$195;
2002
+ return _el$205;
1959
2003
  })(), (() => {
1960
- var _el$197 = _$createElement("span");
1961
- _$insert(_el$197, () => goal().config.checks.join(", ").slice(0, 100));
1962
- _$effect((_$p) => _$setProp(_el$197, "style", {
2004
+ var _el$207 = _$createElement("span");
2005
+ _$insert(_el$207, () => goal().config.checks.join(", ").slice(0, 100));
2006
+ _$effect((_$p) => _$setProp(_el$207, "style", {
1963
2007
  fg: theme().textMuted
1964
2008
  }, _$p));
1965
- return _el$197;
2009
+ return _el$207;
1966
2010
  })()] : null;
1967
2011
  })(), null);
1968
2012
  _$insert(_el$122, (() => {
1969
2013
  var _c$16 = _$memo(() => rt()?.evaluatorRejectionCount > 0);
1970
2014
  return () => _c$16() && [(() => {
1971
- var _el$198 = _$createElement("span"), _el$199 = _$createTextNode(`
2015
+ var _el$208 = _$createElement("span"), _el$209 = _$createTextNode(`
1972
2016
  \u26A0 rejections: `);
1973
- _$insertNode(_el$198, _el$199);
1974
- _$effect((_$p) => _$setProp(_el$198, "style", {
2017
+ _$insertNode(_el$208, _el$209);
2018
+ _$effect((_$p) => _$setProp(_el$208, "style", {
1975
2019
  fg: theme().warning
1976
2020
  }, _$p));
1977
- return _el$198;
2021
+ return _el$208;
1978
2022
  })(), (() => {
1979
- var _el$201 = _$createElement("span"), _el$202 = _$createTextNode(` \u2014 `);
1980
- _$insertNode(_el$201, _el$202);
1981
- _$insert(_el$201, () => String(rt().evaluatorRejectionCount), _el$202);
1982
- _$insert(_el$201, () => String(rt().lastRejectionDetails || "").slice(0, 80), null);
1983
- _$effect((_$p) => _$setProp(_el$201, "style", {
2023
+ var _el$211 = _$createElement("span"), _el$212 = _$createTextNode(` \u2014 `);
2024
+ _$insertNode(_el$211, _el$212);
2025
+ _$insert(_el$211, () => String(rt().evaluatorRejectionCount), _el$212);
2026
+ _$insert(_el$211, () => String(rt().lastRejectionDetails || "").slice(0, 80), null);
2027
+ _$effect((_$p) => _$setProp(_el$211, "style", {
1984
2028
  fg: theme().warning
1985
2029
  }, _$p));
1986
- return _el$201;
2030
+ return _el$211;
1987
2031
  })()];
1988
2032
  })(), null);
1989
2033
  _$insert(_el$122, (() => {
1990
2034
  var _c$17 = _$memo(() => rt()?.unknownStatusCount > 0);
1991
2035
  return () => _c$17() && [(() => {
1992
- var _el$203 = _$createElement("span"), _el$204 = _$createTextNode(`
2036
+ var _el$213 = _$createElement("span"), _el$214 = _$createTextNode(`
1993
2037
  \u26A0\uFE0F unreachable: `);
1994
- _$insertNode(_el$203, _el$204);
1995
- _$effect((_$p) => _$setProp(_el$203, "style", {
2038
+ _$insertNode(_el$213, _el$214);
2039
+ _$effect((_$p) => _$setProp(_el$213, "style", {
1996
2040
  fg: theme().error
1997
2041
  }, _$p));
1998
- return _el$203;
2042
+ return _el$213;
1999
2043
  })(), (() => {
2000
- var _el$206 = _$createElement("span"), _el$207 = _$createTextNode(`/3`);
2001
- _$insertNode(_el$206, _el$207);
2002
- _$insert(_el$206, () => String(rt().unknownStatusCount), _el$207);
2003
- _$effect((_$p) => _$setProp(_el$206, "style", {
2044
+ var _el$216 = _$createElement("span"), _el$217 = _$createTextNode(`/3`);
2045
+ _$insertNode(_el$216, _el$217);
2046
+ _$insert(_el$216, () => String(rt().unknownStatusCount), _el$217);
2047
+ _$effect((_$p) => _$setProp(_el$216, "style", {
2004
2048
  fg: theme().error
2005
2049
  }, _$p));
2006
- return _el$206;
2050
+ return _el$216;
2007
2051
  })(), (() => {
2008
- var _el$208 = _$createElement("span");
2009
- _$insertNode(_el$208, _$createTextNode(` \u2014 nudge to recover`));
2010
- _$effect((_$p) => _$setProp(_el$208, "style", {
2052
+ var _el$218 = _$createElement("span");
2053
+ _$insertNode(_el$218, _$createTextNode(` \u2014 nudge to recover`));
2054
+ _$effect((_$p) => _$setProp(_el$218, "style", {
2011
2055
  fg: theme().textMuted
2012
2056
  }, _$p));
2013
- return _el$208;
2057
+ return _el$218;
2014
2058
  })()];
2015
2059
  })(), null);
2016
2060
  _$insert(_el$122, (() => {
2017
2061
  var _c$18 = _$memo(() => !!rt()?.retryAfter);
2018
2062
  return () => _c$18() && [(() => {
2019
- var _el$210 = _$createElement("span"), _el$211 = _$createTextNode(`
2063
+ var _el$220 = _$createElement("span"), _el$221 = _$createTextNode(`
2020
2064
  \u21BB retry in: `);
2021
- _$insertNode(_el$210, _el$211);
2022
- _$effect((_$p) => _$setProp(_el$210, "style", {
2065
+ _$insertNode(_el$220, _el$221);
2066
+ _$effect((_$p) => _$setProp(_el$220, "style", {
2023
2067
  fg: theme().accent
2024
2068
  }, _$p));
2025
- return _el$210;
2069
+ return _el$220;
2026
2070
  })(), (() => {
2027
- var _el$213 = _$createElement("span");
2028
- _$insert(_el$213, () => countdownLabel(rt().retryAfter, clock()));
2029
- _$effect((_$p) => _$setProp(_el$213, "style", {
2071
+ var _el$223 = _$createElement("span");
2072
+ _$insert(_el$223, () => countdownLabel(rt().retryAfter, clock()));
2073
+ _$effect((_$p) => _$setProp(_el$223, "style", {
2030
2074
  fg: theme().accent
2031
2075
  }, _$p));
2032
- return _el$213;
2076
+ return _el$223;
2033
2077
  })()];
2034
2078
  })(), null);
2035
2079
  _$insert(_el$122, (() => {
2036
2080
  var _c$19 = _$memo(() => !!rt()?.nextRunAt);
2037
2081
  return () => _c$19() && [(() => {
2038
- var _el$214 = _$createElement("span"), _el$215 = _$createTextNode(`
2082
+ var _el$224 = _$createElement("span"), _el$225 = _$createTextNode(`
2039
2083
  \u23F0 next run: `);
2040
- _$insertNode(_el$214, _el$215);
2041
- _$effect((_$p) => _$setProp(_el$214, "style", {
2084
+ _$insertNode(_el$224, _el$225);
2085
+ _$effect((_$p) => _$setProp(_el$224, "style", {
2042
2086
  fg: theme().accent
2043
2087
  }, _$p));
2044
- return _el$214;
2088
+ return _el$224;
2045
2089
  })(), (() => {
2046
- var _el$217 = _$createElement("span");
2047
- _$insert(_el$217, () => countdownLabel(rt().nextRunAt, clock()));
2048
- _$effect((_$p) => _$setProp(_el$217, "style", {
2090
+ var _el$227 = _$createElement("span");
2091
+ _$insert(_el$227, () => countdownLabel(rt().nextRunAt, clock()));
2092
+ _$effect((_$p) => _$setProp(_el$227, "style", {
2049
2093
  fg: theme().accent
2050
2094
  }, _$p));
2051
- return _el$217;
2095
+ return _el$227;
2052
2096
  })(), (() => {
2053
- var _el$218 = _$createElement("span"), _el$219 = _$createTextNode(` (`), _el$220 = _$createTextNode(` runs)`);
2054
- _$insertNode(_el$218, _el$219);
2055
- _$insertNode(_el$218, _el$220);
2056
- _$insert(_el$218, () => String(rt().scheduleRunCount || 0), _el$220);
2057
- _$effect((_$p) => _$setProp(_el$218, "style", {
2097
+ var _el$228 = _$createElement("span"), _el$229 = _$createTextNode(` (`), _el$230 = _$createTextNode(` runs)`);
2098
+ _$insertNode(_el$228, _el$229);
2099
+ _$insertNode(_el$228, _el$230);
2100
+ _$insert(_el$228, () => String(rt().scheduleRunCount || 0), _el$230);
2101
+ _$effect((_$p) => _$setProp(_el$228, "style", {
2058
2102
  fg: theme().textMuted
2059
2103
  }, _$p));
2060
- return _el$218;
2104
+ return _el$228;
2061
2105
  })()];
2062
2106
  })(), null);
2063
2107
  _$insert(_el$122, (() => {
2064
2108
  var _c$20 = _$memo(() => !!rt()?.lastError);
2065
2109
  return () => _c$20() && [(() => {
2066
- var _el$221 = _$createElement("span"), _el$222 = _$createTextNode(`
2110
+ var _el$231 = _$createElement("span"), _el$232 = _$createTextNode(`
2067
2111
  \u26A0 `);
2068
- _$insertNode(_el$221, _el$222);
2069
- _$effect((_$p) => _$setProp(_el$221, "style", {
2112
+ _$insertNode(_el$231, _el$232);
2113
+ _$effect((_$p) => _$setProp(_el$231, "style", {
2070
2114
  fg: theme().error
2071
2115
  }, _$p));
2072
- return _el$221;
2116
+ return _el$231;
2073
2117
  })(), (() => {
2074
- var _el$224 = _$createElement("span");
2075
- _$insertNode(_el$224, _$createTextNode(`Error: `));
2076
- _$effect((_$p) => _$setProp(_el$224, "style", {
2118
+ var _el$234 = _$createElement("span");
2119
+ _$insertNode(_el$234, _$createTextNode(`Error: `));
2120
+ _$effect((_$p) => _$setProp(_el$234, "style", {
2077
2121
  fg: theme().error,
2078
2122
  bold: true
2079
2123
  }, _$p));
2080
- return _el$224;
2124
+ return _el$234;
2081
2125
  })(), (() => {
2082
- var _el$226 = _$createElement("span");
2083
- _$insert(_el$226, () => rt().lastError.slice(0, 120));
2084
- _$effect((_$p) => _$setProp(_el$226, "style", {
2126
+ var _el$236 = _$createElement("span");
2127
+ _$insert(_el$236, () => rt().lastError.slice(0, 120));
2128
+ _$effect((_$p) => _$setProp(_el$236, "style", {
2085
2129
  fg: theme().error
2086
2130
  }, _$p));
2087
- return _el$226;
2131
+ return _el$236;
2088
2132
  })()];
2089
2133
  })(), null);
2090
2134
  _$effect((_p$) => {
@@ -2139,29 +2183,29 @@ function LoopDashboard(props) {
2139
2183
  },
2140
2184
  children: (ev) => [`
2141
2185
  `, (() => {
2142
- var _el$227 = _$createElement("span");
2143
- _$insert(_el$227, () => String(ev.type));
2144
- _$effect((_$p) => _$setProp(_el$227, "style", {
2186
+ var _el$237 = _$createElement("span");
2187
+ _$insert(_el$237, () => String(ev.type));
2188
+ _$effect((_$p) => _$setProp(_el$237, "style", {
2145
2189
  fg: eventColor(String(ev.type), theme()),
2146
2190
  bold: true
2147
2191
  }, _$p));
2148
- return _el$227;
2192
+ return _el$237;
2149
2193
  })(), (() => {
2150
- var _el$228 = _$createElement("span"), _el$229 = _$createTextNode(` `);
2151
- _$insertNode(_el$228, _el$229);
2152
- _$insert(_el$228, () => ev.goalID?.slice(0, 8), null);
2153
- _$effect((_$p) => _$setProp(_el$228, "style", {
2194
+ var _el$238 = _$createElement("span"), _el$239 = _$createTextNode(` `);
2195
+ _$insertNode(_el$238, _el$239);
2196
+ _$insert(_el$238, () => ev.goalID?.slice(0, 8), null);
2197
+ _$effect((_$p) => _$setProp(_el$238, "style", {
2154
2198
  fg: theme().textMuted
2155
2199
  }, _$p));
2156
- return _el$228;
2200
+ return _el$238;
2157
2201
  })(), _$memo(() => _$memo(() => !!ev.summary)() && (() => {
2158
- var _el$230 = _$createElement("span"), _el$231 = _$createTextNode(` \u2014 `);
2159
- _$insertNode(_el$230, _el$231);
2160
- _$insert(_el$230, () => String(ev.summary).slice(0, 60), null);
2161
- _$effect((_$p) => _$setProp(_el$230, "style", {
2202
+ var _el$240 = _$createElement("span"), _el$241 = _$createTextNode(` \u2014 `);
2203
+ _$insertNode(_el$240, _el$241);
2204
+ _$insert(_el$240, () => String(ev.summary).slice(0, 60), null);
2205
+ _$effect((_$p) => _$setProp(_el$240, "style", {
2162
2206
  fg: theme().text
2163
2207
  }, _$p));
2164
- return _el$230;
2208
+ return _el$240;
2165
2209
  })())]
2166
2210
  }), null);
2167
2211
  _$effect((_p$) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@bojackduy/opencode-loopd",
4
- "version": "1.9.5",
4
+ "version": "1.9.6",
5
5
  "description": "Codex-inspired background goal engine for OpenCode — autonomous subagents, engine-driven loop, child worker sessions and modal TUI dashboard. Like Claude Code loop for OpenCode.",
6
6
  "type": "module",
7
7
  "license": "AGPL-3.0-or-later",
@@ -2,7 +2,7 @@
2
2
  name: loopd
3
3
  description: "Agent skill for loopd background goals. Teaches when to create goals, how to inspect/steer/pause them, and how to send instructions to workers."
4
4
  metadata:
5
- version: "1.1.0"
5
+ version: "1.2.0"
6
6
  status: active
7
7
  tags: [opencode, loop, goal, background, automation]
8
8
  ---
@@ -46,8 +46,8 @@ Every goal has an **immutable contract** at creation — the source of truth for
46
46
 
47
47
  * **Objective** — semantic requirements (free text, self-contained). The worker derives concrete requirements from it.
48
48
  * **Checks** — deterministic shell commands that **must pass** for `complete_goal` to be accepted. For `workspaceWrite:true` goals they are **mandatory** (explicit `checks` or plugin `defaultChecks`), and they run from `checkCwd` (writers default to project root; artifact-only jobs run from their `artifactDir`).
49
- * **Agent** — any OpenCode agent name: built-in (`build`, `plan`, `explore`, `general`), file `~/.config/opencode/agents/*.md`, or `opencode.jsonc` `agent.*`. Discover with `opencode agent list`. Prefer `subagent`-mode agents for workers; `primary`-mode agents work but may expect user interaction. Optional — falls back to parent session's agent, or plugin `defaultAgent`.
50
- * **Model** — any model as `"providerID/modelID"` (e.g. `openai/gpt-5.6-sol`, `ollama/qwen3.8:27b`, `openrouter/google/gemma-4-31b-it:free`). Discover with `opencode models [provider]`. Sent on every worker prompt; omit to use the agent/session default, or configure `defaultModel` in `opencode.jsonc`.
49
+ * **Agent** — any OpenCode agent name: built-in (`build`, `plan`, `explore`, `general`), file `~/.config/opencode/agents/*.md`, or `opencode.jsonc` `agent.*`. Discover with `opencode agent list`. Prefer `subagent`-mode agents for workers; `primary`-mode agents work but may expect user interaction. Resolution: explicit arg → **calling session's live agent** (read at creation) → `defaultAgent` → OpenCode global default. So a parent in Plan spawns a Plan worker unless told otherwise.
50
+ * **Model** — any model as `"providerID/modelID"` (e.g. `openai/gpt-5.6-sol`, `ollama/qwen3.8:27b`, `openrouter/google/gemma-4-31b-it:free`). Discover with `opencode models [provider]`. Sent on every worker prompt. Resolution: explicit arg → **calling session's live model** → `defaultModel` → agent config → session history → global default. Caveat: OpenCode exposes no live-model field for history-derived models, so those fall through to agent config.
51
51
  * **WorkspaceWrite** — `true` (default) = may touch the shared repo; **only one active writer at a time** is allowed (enforced on `start`/`resume`/`retry` with rollback). Set `false` explicitly for artifact-only/read-only work to allow concurrency.
52
52
  * **Budgets** — `tokenBudget` (tokens) and `costBudget` (dollars, e.g. `0.5`). When either is exceeded the engine aborts the worker mid-turn and sets `budget_limited`. The worker is stopped immediately, not just at the next turn boundary.
53
53
  * **Limits** — `maxTurns` (default 50), `maxNoProgress`, `maxFailures`, `maxEvaluatorRejections` (default 3), `timeoutMs`, `compactEvery`, `progressFile`.
@@ -73,8 +73,8 @@ Use `loopd_create_goal` **after** clarifying the objective with the user (what /
73
73
  loopd_create_goal({
74
74
  name: "short-name",
75
75
  objective: "Detailed description of what the goal should accomplish.",
76
- agent: "researcher", // any agent name; optional, falls back to parent/defaultAgent
77
- model: "openai/gpt-5.6-sol", // any provider/model; optional, falls back to parent/defaultModel
76
+ agent: "researcher", // any agent name; optional, inherits calling session then defaultAgent
77
+ model: "openai/gpt-5.6-sol", // any provider/model; optional, inherits calling session then defaultModel
78
78
  costBudget: 0.5, // optional dollars; aborts worker when exceeded
79
79
  checks: ["npm test"], // mandatory if workspaceWrite:true (or configure defaultChecks)
80
80
  checkCwd: "/project/root", // optional; writers default to project root, readers to artifactDir