@fooksky/lodestar 0.2.4 → 0.2.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/README.md CHANGED
@@ -24,6 +24,8 @@ Lodestar 是 DeepSeek Harness(DSH)的开发工作流插件,帮助 Agent
24
24
 
25
25
  若需要模式入口,进入侧栏「插件」→「已安装」→ `@fooksky/lodestar` 详情,在「包含的组件」中启用默认关闭的 `dev-workflow-preset`,再于新会话开始前选择「Lodestar · 开发工作流」。已有会话可直接加载 skill;选择模式本身不构成实施授权。支持模式指南的宿主可展示「模式说明 / 如何使用」;旧宿主会忽略指南字段,仍可阅读随包文档。完整步骤、能力要求与恢复方法见 [使用指南:模式说明 / 如何使用](skills/dev-workflow/USER_GUIDE.md)。
26
26
 
27
+ 启用预设后,点击 `dev-workflow-preset` 行的配置入口即可勾选能力组、保存或恢复默认。更改用于新会话,已有会话保留原装配。详见 [能力开关](skills/dev-workflow/USER_GUIDE.md#能力开关)。
28
+
27
29
  ## 材料与工作区
28
30
 
29
31
  默认一个独立需求对应一个 change、一条分支、一个 worktree 和一个 Lead。由用户或外部助手先准备工作区;代码、知识、change 材料和需保留的证据均在本 worktree 内维护。
package/package.json CHANGED
@@ -1,24 +1,67 @@
1
1
  {
2
2
  "name": "@fooksky/lodestar",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "license": "MIT",
5
- "repository": { "type": "git", "url": "git+https://github.com/urzz/lodestar.git" },
6
- "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/", "provenance": false },
7
- "keywords": ["dsh", "cordis", "agent", "development", "workflow", "worktree"],
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/urzz/lodestar.git"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "registry": "https://registry.npmjs.org/",
12
+ "provenance": false
13
+ },
14
+ "keywords": [
15
+ "dsh",
16
+ "cordis",
17
+ "agent",
18
+ "development",
19
+ "workflow",
20
+ "worktree"
21
+ ],
8
22
  "type": "module",
9
23
  "description": "Native DSH development workflow skill and structured change state",
10
- "engines": { "node": ">=24", "dsh": ">=0.2.2-rc.1 <0.3.0" },
24
+ "engines": {
25
+ "node": ">=24",
26
+ "dsh": ">=0.2.2-rc.1 <0.3.0"
27
+ },
11
28
  "exports": {
12
29
  ".": "./index.js",
13
30
  "./preset": "./src/preset.js",
31
+ "./client": "./src/client.js",
14
32
  "./cordis.patch.yml": "./cordis.patch.yml",
15
33
  "./package.json": "./package.json",
16
34
  "./locale/*.json": "./locale/*.json"
17
35
  },
18
- "files": ["index.js", "src", "cordis.patch.yml", "skills", "locale", "icon.svg", "README.md", "LICENSE"],
19
- "scripts": { "test": "node --test tests/*.test.js", "check": "node --check index.js && node --check src/state.js && node --check src/store.js && node --check src/tool.js && node --check src/schema.js && node --check src/workspace.js && node --check src/preset.js && node --check src/preset-capabilities.js", "prepack": "pnpm check && pnpm test" },
36
+ "files": [
37
+ "index.js",
38
+ "src",
39
+ "cordis.patch.yml",
40
+ "skills",
41
+ "locale",
42
+ "icon.svg",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "scripts": {
47
+ "test": "node --test tests/*.test.js",
48
+ "check": "node --check index.js && node --check src/state.js && node --check src/store.js && node --check src/tool.js && node --check src/schema.js && node --check src/workspace.js && node --check src/preset.js && node --check src/preset-capabilities.js && node --check src/client.js",
49
+ "prepack": "pnpm check && pnpm test"
50
+ },
20
51
  "icon": "./icon.svg",
21
- "dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
52
+ "dsh": {
53
+ "bundle": {
54
+ "patch": "./cordis.patch.yml"
55
+ },
56
+ "client": {
57
+ "platform": "web",
58
+ "inject": [
59
+ "@deepseek-ai/dsh-client-locale",
60
+ "@deepseek-ai/dsh-client-ui-settings",
61
+ "@deepseek-ai/dsh-client-ui-plugin-manager"
62
+ ]
63
+ }
64
+ },
22
65
  "peerDependencies": {
23
66
  "@deepseek-ai/cordis": "~4.1.0",
24
67
  "@deepseek-ai/dsh-skill": "0.2.2-rc.1",
@@ -32,16 +75,39 @@
32
75
  "@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1"
33
76
  },
34
77
  "peerDependenciesMeta": {
35
- "@deepseek-ai/cordis": { "optional": true },
36
- "@deepseek-ai/dsh-skill": { "optional": true },
37
- "@deepseek-ai/dsh-tools": { "optional": true },
38
- "@deepseek-ai/dsh-fs": { "optional": true },
39
- "@deepseek-ai/dsh-working-directory": { "optional": true },
40
- "@deepseek-ai/dsh-sandbox-policy": { "optional": true },
41
- "@deepseek-ai/dsh-subprocess": { "optional": true },
42
- "@deepseek-ai/dsh-sandbox": { "optional": true },
43
- "@deepseek-ai/dsh-agent-preset-registry": { "optional": true },
44
- "@deepseek-ai/dsh-system-prompt": { "optional": true }
45
- },
46
- "dependencies": { "@deepseek-ai/schemastery": "~3.19.0", "zod": "^4.6.7" }
78
+ "@deepseek-ai/cordis": {
79
+ "optional": true
80
+ },
81
+ "@deepseek-ai/dsh-skill": {
82
+ "optional": true
83
+ },
84
+ "@deepseek-ai/dsh-tools": {
85
+ "optional": true
86
+ },
87
+ "@deepseek-ai/dsh-fs": {
88
+ "optional": true
89
+ },
90
+ "@deepseek-ai/dsh-working-directory": {
91
+ "optional": true
92
+ },
93
+ "@deepseek-ai/dsh-sandbox-policy": {
94
+ "optional": true
95
+ },
96
+ "@deepseek-ai/dsh-subprocess": {
97
+ "optional": true
98
+ },
99
+ "@deepseek-ai/dsh-sandbox": {
100
+ "optional": true
101
+ },
102
+ "@deepseek-ai/dsh-agent-preset-registry": {
103
+ "optional": true
104
+ },
105
+ "@deepseek-ai/dsh-system-prompt": {
106
+ "optional": true
107
+ }
108
+ },
109
+ "dependencies": {
110
+ "@deepseek-ai/schemastery": "~3.19.0",
111
+ "zod": "^4.6.7"
112
+ }
47
113
  }
@@ -25,7 +25,9 @@
25
25
 
26
26
  ### 能力开关
27
27
 
28
- 在插件配置中编辑 `dev-workflow-preset` 组件的 `capabilities`;支持配置表单的宿主会显示布尔开关,也可在该组件的 `config` 中填写:
28
+ 在侧栏「插件」→「已安装」→ Lodestar 详情,先启用 `dev-workflow-preset` 组件,再点击该行的配置入口。页面提供 14 组能力勾选项,点击「保存更改」后生效;「恢复默认」会在保存时移除用户覆盖,继承部署配置(未配置时全部开启)。离开页面不会保存草稿;保存失败时会保留草稿并显示原因。配置冲突时可「放弃更改」后重新编辑。
29
+
30
+ 也可在该组件的 `config` 中填写:
29
31
 
30
32
  ```yaml
31
33
  capabilities:
package/src/client.js ADDED
@@ -0,0 +1,134 @@
1
+ // Browser module delivered by DSH; React is supplied by the host module table.
2
+ window.__ModuleLoader__.load({
3
+ id: '@fooksky/lodestar',
4
+ factory(require) {
5
+ const React = require('react');
6
+ const h = React.createElement;
7
+ const labels = {
8
+ context: '角色、项目规则和时间上下文', terminal: '终端和后台任务',
9
+ files: '文件读写、编辑和搜索', scheduling: '定时任务', skills: '技能发现和加载',
10
+ goals: '持续目标', planning: '原生计划模式', compaction: '上下文压缩',
11
+ delegation: '子 Agent 和 workflow', questions: '用户提问', todos: '任务进度',
12
+ web: '网页搜索和抓取', present: '文件交付', pluginManagement: '插件管理',
13
+ };
14
+ const enLabels = {
15
+ context: 'Role, project rules and time context', terminal: 'Terminal and background jobs',
16
+ files: 'File editing and search', scheduling: 'Scheduled tasks', skills: 'Skill discovery and loading',
17
+ goals: 'Persistent goals', planning: 'Plan mode', compaction: 'Context compaction',
18
+ delegation: 'Subagents and workflow', questions: 'User questions', todos: 'Task progress',
19
+ web: 'Web search and fetch', present: 'File delivery', pluginManagement: 'Plugin management',
20
+ };
21
+ const groups = [
22
+ { zh: '基础环境', en: 'Environment', keys: ['context', 'files', 'terminal', 'compaction'] },
23
+ { zh: '执行与协作', en: 'Execution and collaboration', keys: ['planning', 'goals', 'delegation', 'questions', 'todos'] },
24
+ { zh: '信息与交付', en: 'Information and delivery', keys: ['skills', 'web', 'present'] },
25
+ { zh: '扩展管理', en: 'Extensions', keys: ['scheduling', 'pluginManagement'] },
26
+ ];
27
+ // Scoped styles use the same semantic tokens and control dimensions as DSH.
28
+ const styles = `
29
+ .lodestar-config { width: 100%; max-width: 720px; color: var(--dsw-alias-label-primary); font: 14px/1.6 var(--dsw-font-family); }
30
+ .lodestar-config * { box-sizing: border-box; }
31
+ .lodestar-config p { margin: 0; }
32
+ .lodestar-config .lodestar-intro { color: var(--dsw-alias-label-tertiary); font-size: 13px; margin-bottom: 24px; max-width: 68ch; }
33
+ .lodestar-config .lodestar-group { border: 0; padding: 0; margin: 0 0 24px; min-width: 0; }
34
+ .lodestar-config .lodestar-group legend { padding: 0 0 8px; font-size: 15px; font-weight: 600; }
35
+ .lodestar-config .lodestar-row { display: flex; flex-direction: row-reverse; justify-content: space-between; align-items: center; gap: 24px; min-height: 48px; padding: 10px 0; border-bottom: 1px solid var(--dsw-alias-border-l1); cursor: pointer; overflow-wrap: anywhere; }
36
+ .lodestar-config .lodestar-row:has(input:disabled) { cursor: default; }
37
+ .lodestar-config .lodestar-switch { appearance: none; position: relative; flex: 0 0 36px; width: 36px; height: 20px; margin: 0; border: 0; border-radius: 999px; background: var(--dsw-alias-border-l3); cursor: inherit; }
38
+ .lodestar-config .lodestar-switch::before { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--dsw-alias-switch-thumb); transition: transform .15s; }
39
+ .lodestar-config .lodestar-switch:checked { background: var(--dsw-alias-brand-primary); }
40
+ .lodestar-config .lodestar-switch:checked::before { transform: translateX(16px); background: var(--dsw-alias-label-primary-inverted); }
41
+ .lodestar-config .lodestar-switch:disabled { opacity: .5; }
42
+ .lodestar-config .lodestar-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l2); }
43
+ .lodestar-config .lodestar-button { min-height: 36px; padding: 6px 14px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md); background: transparent; color: var(--dsw-alias-label-primary); font: inherit; cursor: pointer; }
44
+ .lodestar-config .lodestar-primary { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-inverted); border-color: transparent; }
45
+ .lodestar-config .lodestar-reset { margin-inline-start: auto; }
46
+ .lodestar-config .lodestar-button:disabled { opacity: .4; cursor: default; }
47
+ .lodestar-config :is(input, button):focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary); outline-offset: 3px; }
48
+ .lodestar-config .lodestar-message { font-size: 13px; color: var(--dsw-alias-label-secondary); margin-bottom: 12px; overflow-wrap: anywhere; }
49
+ .lodestar-config .lodestar-error { color: var(--dsw-alias-state-error-primary); }
50
+ @media (hover: hover) {
51
+ .lodestar-config .lodestar-button:not(:disabled):hover { background: var(--dsw-alias-interactive-bg-hover); }
52
+ .lodestar-config .lodestar-primary:not(:disabled):hover { background: var(--dsw-alias-button-primary-hover); }
53
+ }
54
+ @media (max-width: 400px) { .lodestar-config .lodestar-reset { margin-inline-start: 0; } }
55
+ @media (prefers-reduced-motion: reduce) { .lodestar-config .lodestar-switch::before { transition: none; } }
56
+ `;
57
+ const strings = {
58
+ zh: { title: '预设能力', summary: '选择开发工作流可使用的能力。',
59
+ intro: '保存后供新会话使用;已有会话保留原装配。开关仅控制此预设。',
60
+ unavailable: '请先启用 dev-workflow-preset 组件,再打开配置。', loading: '正在读取配置…',
61
+ readonly: '当前配置为只读。', save: '保存更改', saving: '正在保存…', reset: '恢复默认',
62
+ cancel: '放弃更改', saved: '已保存,新会话将使用更新后的能力。',
63
+ conflict: '未能保存。配置可能已变化,请放弃更改后重试。', resetHint: '恢复默认将在保存时应用。' },
64
+ en: { title: 'Preset capabilities', summary: 'Choose capabilities for the development workflow.',
65
+ intro: 'Changes apply to new sessions. Existing sessions keep their composition. These switches affect this preset only.',
66
+ unavailable: 'Enable the dev-workflow-preset component, then open configuration.', loading: 'Loading configuration…',
67
+ readonly: 'This configuration is read-only.', save: 'Save changes', saving: 'Saving…', reset: 'Restore defaults',
68
+ cancel: 'Discard changes', saved: 'Saved. New sessions will use the updated capabilities.',
69
+ conflict: 'Could not save. Configuration may have changed. Discard changes and try again.', resetHint: 'Defaults will be restored when you save.' },
70
+ };
71
+ return {
72
+ inject: ['slots', 'locale'],
73
+ apply(ctx) {
74
+ function Capabilities({ view, form }) {
75
+ const locale = React.useSyncExternalStore(listener => ctx.locale.subscribe(listener), () => ctx.locale.getSnapshot());
76
+ const zh = locale.active.startsWith('zh');
77
+ const t = strings[zh ? 'zh' : 'en'];
78
+ const [draft, setDraft] = React.useState(null);
79
+ const [busy, setBusy] = React.useState(false);
80
+ const [message, setMessage] = React.useState(null);
81
+ if (view === 'summary') return h('p', null, t.summary);
82
+ const state = form?.state;
83
+ if (!state || state.status === 'unavailable') return h('p', null, t.unavailable);
84
+ if (state.status === 'loading') return h('p', { role: 'status' }, t.loading);
85
+ const values = draft?.values ?? state.value.capabilities;
86
+ const editable = state.writable && !busy;
87
+ const begin = () => ({ values: { ...state.value.capabilities }, revision: state.revision, reset: false });
88
+ async function save(event) {
89
+ event.preventDefault();
90
+ setBusy(true);
91
+ setMessage(null);
92
+ try {
93
+ const ops = Object.keys(labels).map(key => draft.reset
94
+ ? { op: 'unset', path: ['capabilities', key] }
95
+ : { op: 'set', path: ['capabilities', key], value: draft.values[key] });
96
+ const accepted = await form.mutate(ops, draft.revision);
97
+ if (accepted) setDraft(null);
98
+ setMessage({ error: !accepted, text: accepted ? t.saved : t.conflict });
99
+ } catch (error) {
100
+ setMessage({ error: true, text: error.message });
101
+ } finally { setBusy(false); }
102
+ }
103
+ return h('form', { onSubmit: save, className: 'lodestar-config', 'aria-label': t.title, 'aria-busy': busy },
104
+ h('style', null, styles),
105
+ h('p', { className: 'lodestar-intro' }, t.intro),
106
+ !state.writable && h('p', { className: 'lodestar-message' }, t.readonly),
107
+ ...groups.map(group => h('fieldset', { key: group.en, disabled: !editable, className: 'lodestar-group' },
108
+ h('legend', null, zh ? group.zh : group.en),
109
+ ...group.keys.map(key => h('label', { key, className: 'lodestar-row' },
110
+ h('input', { type: 'checkbox', role: 'switch', className: 'lodestar-switch', checked: values[key], onChange: event => {
111
+ const next = draft ?? begin();
112
+ setDraft({ ...next, reset: false, values: { ...next.values, [key]: event.target.checked } });
113
+ setMessage(null);
114
+ } }), (zh ? labels : enLabels)[key])))),
115
+ draft?.reset && h('p', { role: 'status', className: 'lodestar-message' }, t.resetHint),
116
+ message && h('p', { role: message.error ? 'alert' : 'status',
117
+ className: `lodestar-message${message.error ? ' lodestar-error' : ''}` }, message.text),
118
+ h('div', { className: 'lodestar-actions' },
119
+ h('button', { type: 'submit', className: 'lodestar-button lodestar-primary', disabled: !editable || !draft }, busy ? t.saving : t.save),
120
+ draft && h('button', { type: 'button', className: 'lodestar-button', disabled: busy, onClick: () => { setDraft(null); setMessage(null); } }, t.cancel),
121
+ h('button', { type: 'button', className: 'lodestar-button lodestar-reset', disabled: !editable, onClick: () => {
122
+ const base = state.base?.capabilities ?? {};
123
+ setDraft({ ...(draft ?? begin()), reset: true,
124
+ values: Object.fromEntries(Object.keys(labels).map(key => [key, base[key] ?? true])) });
125
+ setMessage(null);
126
+ } }, t.reset)));
127
+ }
128
+ ctx.effect(() => ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
129
+ name: 'plugins.row.config', key: '@fooksky/lodestar#dev-workflow-preset',
130
+ }, Capabilities)));
131
+ },
132
+ };
133
+ },
134
+ });
package/src/preset.js CHANGED
@@ -12,7 +12,7 @@ export const inject = ['tools', 'agentPresets', 'systemPrompt'];
12
12
  export const Config = z.object({
13
13
  entry: z.boolean().default(false).hidden(),
14
14
  capabilities: z.object(Object.fromEntries(Object.entries(capabilityGroups).map(([key, group]) =>
15
- [key, z.boolean().default(true).description(group.label)]))).default(defaultCapabilities).description('能力开关;默认对齐标准模式,仅控制本 preset 装配'),
15
+ [key, z.boolean().default(true).description(group.label)]))).default(defaultCapabilities).description('能力开关;默认对齐标准模式,仅控制本 preset 装配').volatile(),
16
16
  });
17
17
  export const requiredTools = expectedTools(defaultCapabilities);
18
18
  export const modeText = '本会话采用开发工作流。开发变更先加载 lodestar,定位或创建 change,按当前阶段继续。只调查、解释或设计时遵守该范围;选择模式不构成实施、提交、发布或安装授权。受委派成员执行任务包并向 Lead 回报,不重复启动 Lead 需求确认流程。浏览器、记忆和 Teams 等扩展沿用宿主部署;普通 subagent 按任务需要使用。';
@@ -28,7 +28,11 @@ export function createDefinition(capabilities = defaultCapabilities) {
28
28
  }
29
29
  export const definition = createDefinition();
30
30
  export async function apply(ctx, config = {}) {
31
- const { entry, capabilities } = Config(config);
31
+ // Cordis resolves Config before activation; direct callers provide raw config.
32
+ const resolved = config.capabilities?.get ? config : Config(config);
33
+ const { entry } = resolved;
34
+ const readCapabilities = () => resolved.capabilities.get();
35
+ const capabilities = readCapabilities();
32
36
  if (entry) {
33
37
  if (!ctx.systemPrompt) throw new Error('dev-workflow mode requires systemPrompt');
34
38
  const dispose = ctx.systemPrompt.section({
@@ -45,7 +49,29 @@ export async function apply(ctx, config = {}) {
45
49
  ctx.effect(() => dispose);
46
50
  } else {
47
51
  if (!ctx.agentPresets) throw new Error('dev-workflow preset requires agentPresets');
48
- const dispose = await ctx.agentPresets.register(createDefinition(capabilities));
49
- ctx.effect(() => dispose);
52
+ const registry = ctx.agentPresets;
53
+ await ctx.effect(async () => {
54
+ let dispose;
55
+ let closed = false;
56
+ const replace = async () => {
57
+ if (closed) return;
58
+ // Same-id registration is rejected. Retiring first preserves existing
59
+ // users of the old generation while new scopes join the replacement.
60
+ await dispose?.();
61
+ dispose = undefined;
62
+ if (!closed) dispose = await registry.register(createDefinition(readCapabilities()));
63
+ };
64
+ let pending = replace();
65
+ ctx.on('loader/volatile-update', paths => {
66
+ if (!paths.some(path => path[0] === 'capabilities')) return;
67
+ pending = pending.then(replace);
68
+ pending.catch(error => ctx.logger.error(error));
69
+ });
70
+ await pending;
71
+ return async () => {
72
+ closed = true;
73
+ try { await pending; } finally { await dispose?.(); }
74
+ };
75
+ });
50
76
  }
51
77
  }