oxe-cc 1.5.1 → 1.7.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +45 -0
- package/README.md +19 -15
- package/bin/lib/oxe-agent-install.cjs +125 -24
- package/bin/lib/oxe-dashboard.cjs +21 -5
- package/bin/lib/oxe-project-health.cjs +120 -42
- package/bin/lib/oxe-release.cjs +77 -4
- package/bin/oxe-cc.js +155 -78
- package/commands/oxe/debug.md +6 -1
- package/commands/oxe/discuss.md +7 -2
- package/commands/oxe/execute.md +7 -2
- package/commands/oxe/plan-agent.md +7 -2
- package/commands/oxe/plan.md +7 -2
- package/commands/oxe/scan.md +6 -1
- package/commands/oxe/spec.md +6 -1
- package/commands/oxe/verify.md +6 -1
- package/docs/CONTENT-MIGRATION-AUDIT.md +49 -0
- package/docs/RELEASE-READINESS.md +8 -0
- package/docs/RUNTIME-SMOKE-MATRIX.md +9 -2
- package/lib/runtime/compiler/graph-compiler.js +32 -0
- package/lib/runtime/context/context-pack-builder.d.ts +15 -0
- package/lib/runtime/context/context-pack-builder.js +78 -0
- package/lib/runtime/events/catalog.d.ts +1 -1
- package/lib/runtime/events/catalog.js +5 -0
- package/lib/runtime/executor/action-tool-map.d.ts +3 -0
- package/lib/runtime/executor/action-tool-map.js +41 -0
- package/lib/runtime/executor/built-in-tools.d.ts +8 -0
- package/lib/runtime/executor/built-in-tools.js +267 -0
- package/lib/runtime/executor/index.d.ts +6 -0
- package/lib/runtime/executor/index.js +12 -0
- package/lib/runtime/executor/llm-task-executor.d.ts +29 -0
- package/lib/runtime/executor/llm-task-executor.js +138 -0
- package/lib/runtime/executor/node-prompt-builder.d.ts +3 -0
- package/lib/runtime/executor/node-prompt-builder.js +36 -0
- package/lib/runtime/executor/stream-completion.d.ts +38 -0
- package/lib/runtime/executor/stream-completion.js +105 -0
- package/lib/runtime/index.d.ts +1 -0
- package/lib/runtime/index.js +2 -0
- package/lib/runtime/models/failure.d.ts +5 -0
- package/lib/runtime/models/failure.js +2 -0
- package/lib/runtime/plugins/capability-adapter.d.ts +9 -0
- package/lib/runtime/plugins/capability-adapter.js +111 -8
- package/lib/runtime/plugins/plugin-abi.d.ts +8 -0
- package/lib/runtime/plugins/plugin-registry.d.ts +2 -1
- package/lib/runtime/plugins/plugin-registry.js +6 -1
- package/lib/runtime/reducers/run-state-reducer.js +39 -2
- package/lib/runtime/scheduler/scheduler.d.ts +14 -2
- package/lib/runtime/scheduler/scheduler.js +131 -11
- package/lib/runtime/verification/verification-manifest.d.ts +5 -2
- package/lib/sdk/index.cjs +10 -5
- package/lib/sdk/index.d.ts +21 -10
- package/oxe/agents/oxe-assumptions-analyzer.md +136 -0
- package/oxe/agents/oxe-codebase-mapper.md +142 -0
- package/oxe/agents/oxe-debugger.md +145 -0
- package/oxe/agents/oxe-executor.md +139 -0
- package/oxe/agents/oxe-integration-checker.md +142 -0
- package/oxe/agents/oxe-plan-checker.md +143 -0
- package/oxe/agents/oxe-planner.md +151 -0
- package/oxe/agents/oxe-research-synthesizer.md +146 -0
- package/oxe/agents/oxe-researcher.md +163 -0
- package/oxe/agents/oxe-ui-auditor.md +151 -0
- package/oxe/agents/oxe-ui-checker.md +157 -0
- package/oxe/agents/oxe-ui-researcher.md +179 -0
- package/oxe/agents/oxe-validation-auditor.md +154 -0
- package/oxe/agents/oxe-verifier.md +132 -0
- package/oxe/personas/README.md +91 -39
- package/oxe/personas/architect.md +149 -37
- package/oxe/personas/db-specialist.md +149 -36
- package/oxe/personas/debugger.md +155 -38
- package/oxe/personas/executor.md +164 -38
- package/oxe/personas/planner.md +165 -36
- package/oxe/personas/researcher.md +148 -35
- package/oxe/personas/ui-specialist.md +164 -36
- package/oxe/personas/verifier.md +174 -39
- package/oxe/templates/CONFIG.md +3 -3
- package/oxe/templates/EXECUTION-RUNTIME.template.md +1 -1
- package/oxe/templates/FIXTURE-PACK.template.json +29 -22
- package/oxe/templates/FIXTURE-PACK.template.md +20 -11
- package/oxe/templates/IMPLEMENTATION-PACK.template.json +55 -39
- package/oxe/templates/IMPLEMENTATION-PACK.template.md +28 -16
- package/oxe/templates/INVESTIGATION.template.md +38 -38
- package/oxe/templates/PLAN.template.md +63 -32
- package/oxe/templates/REFERENCE-ANCHORS.template.md +18 -14
- package/oxe/templates/RESEARCH.template.md +11 -11
- package/oxe/templates/SPEC.template.md +6 -6
- package/oxe/templates/SUMMARY.template.md +33 -3
- package/oxe/templates/config.template.json +1 -1
- package/oxe/workflows/debug.md +9 -7
- package/oxe/workflows/execute.md +31 -28
- package/oxe/workflows/forensics.md +5 -3
- package/oxe/workflows/milestone.md +12 -12
- package/oxe/workflows/next.md +1 -1
- package/oxe/workflows/plan.md +409 -132
- package/oxe/workflows/references/adaptive-discovery.md +27 -27
- package/oxe/workflows/references/flow-robustness-contract.md +80 -80
- package/oxe/workflows/references/session-path-resolution.md +71 -71
- package/oxe/workflows/references/workflow-runtime-contracts.json +127 -127
- package/oxe/workflows/scan.md +355 -69
- package/oxe/workflows/spec.md +302 -9
- package/oxe/workflows/ui-review.md +5 -4
- package/oxe/workflows/ui-spec.md +4 -3
- package/oxe/workflows/verify.md +12 -9
- package/oxe/workflows/workstream.md +16 -16
- package/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/runtime/src/compiler/graph-compiler.ts +40 -0
- package/packages/runtime/src/context/context-pack-builder.ts +80 -0
- package/packages/runtime/src/events/catalog.ts +5 -0
- package/packages/runtime/src/executor/action-tool-map.ts +46 -0
- package/packages/runtime/src/executor/built-in-tools.ts +276 -0
- package/packages/runtime/src/executor/index.ts +6 -0
- package/packages/runtime/src/executor/llm-task-executor.ts +194 -0
- package/packages/runtime/src/executor/node-prompt-builder.ts +45 -0
- package/packages/runtime/src/executor/stream-completion.ts +145 -0
- package/packages/runtime/src/index.ts +3 -0
- package/packages/runtime/src/models/failure.ts +11 -0
- package/packages/runtime/src/plugins/capability-adapter.ts +117 -10
- package/packages/runtime/src/plugins/plugin-abi.ts +9 -0
- package/packages/runtime/src/plugins/plugin-registry.ts +10 -1
- package/packages/runtime/src/reducers/run-state-reducer.ts +59 -2
- package/packages/runtime/src/scheduler/scheduler.ts +152 -14
- package/packages/runtime/src/verification/verification-manifest.ts +12 -8
- package/vscode-extension/oxe-agents-1.6.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.7.0.vsix +0 -0
- package/vscode-extension/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Content Migration Audit
|
|
2
|
+
|
|
3
|
+
Este documento registra a triagem interna de conteúdo usado como insumo para fortalecer o OXE. Ele não é instalado em projetos de usuário e não deve ser referenciado por templates, workflows, comandos, README ou agentes finais.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
- Public artifacts must use OXE naming, paths, commands and runtime contracts only.
|
|
8
|
+
- No public template/workflow/command/agent may reference legacy product names, paths, tool commands or command namespaces.
|
|
9
|
+
- Source material is reauthored, not copied literally.
|
|
10
|
+
|
|
11
|
+
## Templates
|
|
12
|
+
|
|
13
|
+
| Source theme | Decision | OXE target |
|
|
14
|
+
|--------------|----------|------------|
|
|
15
|
+
| Detailed phase plan contract | merge | `PLAN.template.md`, `IMPLEMENTATION-PACK.*`, `/oxe-plan` |
|
|
16
|
+
| Context and discovery templates | merge | context packs, `RESEARCH.template.md`, `INVESTIGATION.template.md`, `REFERENCE-ANCHORS.template.md` |
|
|
17
|
+
| Requirements, project, roadmap, state | merge | `SPEC.template.md`, `ROADMAP.template.md`, `STATE.md` |
|
|
18
|
+
| Validation, verification and UAT | merge | `/oxe-verify`, runtime evidence, `VERIFY.md` projection |
|
|
19
|
+
| UI contract | merge | `/oxe-spec --ui`, `/oxe-ui-spec`, `/oxe-ui-review` |
|
|
20
|
+
| Debug templates | merge | `/oxe-debug`, `/oxe-forensics`, `oxe-debugger` |
|
|
21
|
+
| Session summaries and setup | merge | `SUMMARY.template.md`, `RESUME.template.md`, checkpoints |
|
|
22
|
+
| Runtime-specific instruction files | inspiration_only | OXE multi-runtime generators |
|
|
23
|
+
| Developer profile/preferences | reject | future consented profile feature only |
|
|
24
|
+
|
|
25
|
+
## Workflows
|
|
26
|
+
|
|
27
|
+
| Source theme | Decision | OXE target |
|
|
28
|
+
|--------------|----------|------------|
|
|
29
|
+
| New project and milestone discovery | merge | `/oxe-spec`, `/oxe-plan` |
|
|
30
|
+
| Phase planning and review loop | merge | `/oxe-plan`, rationality gate |
|
|
31
|
+
| Plan execution by wave/task | merge | `/oxe-execute` |
|
|
32
|
+
| Verification, validation gaps and UAT | merge | `/oxe-verify` |
|
|
33
|
+
| Codebase mapping and research | merge | `/oxe-scan`, `/oxe-spec --research` |
|
|
34
|
+
| Debug and incident diagnosis | merge | `/oxe-debug`, `/oxe-forensics` |
|
|
35
|
+
| UI design and review | merge | `/oxe-spec --ui`, `/oxe-ui-review` |
|
|
36
|
+
| Progress, next, pause and resume | merge | `/oxe`, `/oxe-next`, `/oxe-session` |
|
|
37
|
+
| Workstreams and isolated workspace concepts | merge | `/oxe-workstream`, runtime agents |
|
|
38
|
+
| Autonomous workflow | reject | OXE keeps governed execution |
|
|
39
|
+
| PR branch/ship workflow | inspiration_only | OXE keeps local commit and separate `runtime promote` |
|
|
40
|
+
| Community/settings/update flows | reject | OXE already has administrative UX |
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
Legacy command wrappers are not copied. They are used only as intent inventory and mapped into canonical OXE workflows before generated surfaces are regenerated.
|
|
45
|
+
|
|
46
|
+
## Agents
|
|
47
|
+
|
|
48
|
+
Specialized agents were reauthored as OXE-native contracts in `oxe/agents/`. They use `.oxe/`, OXE workflows, runtime enterprise, evidence store, personas and `plan-agents.json` only.
|
|
49
|
+
|
|
@@ -13,10 +13,13 @@ npx oxe-cc doctor --release --write-manifest
|
|
|
13
13
|
|
|
14
14
|
O `doctor --release` deve bloquear a publicação quando encontrar:
|
|
15
15
|
|
|
16
|
+
- árvore canónica `oxe/workflows/`, `oxe/workflows/references/` ou `commands/oxe/` ausente
|
|
17
|
+
- `workflow-runtime-contracts.json` ausente ou inválido
|
|
16
18
|
- drift de versão entre `package.json`, `packages/runtime/package.json`, `vscode-extension/package.json`, `README.md`, `CHANGELOG.md` e banner
|
|
17
19
|
- topo do `CHANGELOG` ausente, sem data ou sem highlights
|
|
18
20
|
- runtime não compilado em `lib/runtime/index.js`
|
|
19
21
|
- wrappers dirty após `sync-runtime-metadata` e `sync:cursor`
|
|
22
|
+
- drift semântico entre workflows canónicos e superfícies geradas
|
|
20
23
|
- ausência ou falha dos relatórios obrigatórios da release
|
|
21
24
|
|
|
22
25
|
## Relatórios obrigatórios
|
|
@@ -44,3 +47,8 @@ O pipeline de CI e o pipeline de release devem rodar o mesmo gate:
|
|
|
44
47
|
3. `npm run release:doctor`
|
|
45
48
|
|
|
46
49
|
Se qualquer etapa falhar, a release não está pronta.
|
|
50
|
+
|
|
51
|
+
## Observações operacionais
|
|
52
|
+
|
|
53
|
+
- `status` e `status --full` distinguem agora `workspaceMode: product_package` de `workspaceMode: oxe_project`.
|
|
54
|
+
- No repositório do pacote, readiness passa a ser de publicação; o CLI deixa de bloquear por ausência de `PLAN.md` executável quando não há ciclo ativo declarado.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OXE — Runtime Smoke Matrix
|
|
2
2
|
|
|
3
|
-
> Estado de suporte por runtime de IA. Atualizado em v1.
|
|
3
|
+
> Estado de suporte por runtime de IA. Atualizado em v1.7.0.
|
|
4
4
|
>
|
|
5
5
|
> `✓` = suportado e testado | `~` = suportado parcialmente | `✗` = não suportado | `?` = não testado
|
|
6
6
|
|
|
@@ -60,11 +60,18 @@ Este documento descreve a matrix estável da release. O artefato operacional con
|
|
|
60
60
|
|
|
61
61
|
### Codex CLI
|
|
62
62
|
|
|
63
|
-
- Lê `.
|
|
63
|
+
- Lê `.codex/prompts/` como entrypoint e depende também de skills em `.agents/skills/oxe/`
|
|
64
|
+
- A smoke matrix desta release valida **prompts e skills**; falha se um dos dois lados estiver ausente ou sem a política de resolução de workflow
|
|
64
65
|
- `verify` parcial: gera VERIFY.md mas evidências são menos detalhadas (sem análise de AST)
|
|
65
66
|
- `runtime-first` não suportado: Codex CLI não tem acesso a subprocessos Node
|
|
66
67
|
- **Limitação:** não suporta contexto multi-arquivo simultâneo em tasks longas
|
|
67
68
|
|
|
69
|
+
### Escopo de instalação
|
|
70
|
+
|
|
71
|
+
- `--local` define o layout do repositório (`oxe/` + `.oxe/` vs só `.oxe/`)
|
|
72
|
+
- `--ide-local` define que a integração do runtime deve ser instalada no próprio projeto
|
|
73
|
+
- Para Codex, `install --codex --ide-local` é o fluxo correto quando a intenção é materializar `.codex/` e `.agents/` no repositório atual
|
|
74
|
+
|
|
68
75
|
### Gemini CLI
|
|
69
76
|
|
|
70
77
|
- `plan` e `execute` parciais: segue o fluxo mas pode divergir dos critérios A* do SPEC
|
|
@@ -95,6 +95,38 @@ function validateGraph(graph) {
|
|
|
95
95
|
break;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
// Validate mutation_scope conflicts between parallel nodes in the same wave
|
|
99
|
+
const waveMap = new Map();
|
|
100
|
+
for (const [id, node] of graph.nodes) {
|
|
101
|
+
const list = waveMap.get(node.wave) ?? [];
|
|
102
|
+
waveMap.set(node.wave, [...list, id]);
|
|
103
|
+
}
|
|
104
|
+
for (const [wave, waveNodeIds] of waveMap) {
|
|
105
|
+
for (let i = 0; i < waveNodeIds.length; i++) {
|
|
106
|
+
for (let j = i + 1; j < waveNodeIds.length; j++) {
|
|
107
|
+
const idA = waveNodeIds[i];
|
|
108
|
+
const idB = waveNodeIds[j];
|
|
109
|
+
const a = graph.nodes.get(idA);
|
|
110
|
+
const b = graph.nodes.get(idB);
|
|
111
|
+
const notDependent = !a.depends_on.includes(idB) && !b.depends_on.includes(idA);
|
|
112
|
+
if (notDependent && a.mutation_scope.length > 0 && b.mutation_scope.length > 0) {
|
|
113
|
+
const overlap = a.mutation_scope.filter(p => b.mutation_scope.includes(p));
|
|
114
|
+
if (overlap.length > 0) {
|
|
115
|
+
errors.push(`Wave ${wave}: nodes "${idA}" and "${idB}" mutate the same paths in parallel: ${overlap.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Validate wave ordering: a node must only depend on nodes from earlier waves
|
|
122
|
+
for (const [id, node] of graph.nodes) {
|
|
123
|
+
for (const dep of node.depends_on) {
|
|
124
|
+
const depNode = graph.nodes.get(dep);
|
|
125
|
+
if (depNode && depNode.wave >= node.wave) {
|
|
126
|
+
errors.push(`Node "${id}" (wave ${node.wave}) depends on "${dep}" (wave ${depNode.wave}) — dependency must come from an earlier wave`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
98
130
|
return errors;
|
|
99
131
|
}
|
|
100
132
|
function toSerializable(graph) {
|
|
@@ -43,6 +43,21 @@ export declare class ContextPackBuilder {
|
|
|
43
43
|
build(workItem: WorkItem, state: RunState, evidenceItems: Evidence[], evidenceContents: Map<string, string>, lessons: LessonMetric[]): ContextPack;
|
|
44
44
|
/** Convenience: build with no evidence, just lessons and state summary */
|
|
45
45
|
buildLightweight(workItem: WorkItem, state: RunState, lessons: LessonMetric[]): ContextPack;
|
|
46
|
+
/**
|
|
47
|
+
* Remove artifacts with lowest relevance until the pack fits within targetTokens.
|
|
48
|
+
* Artifacts already sorted by relevance; we trim the tail.
|
|
49
|
+
*/
|
|
50
|
+
compact(pack: ContextPack, targetTokens: number): ContextPack;
|
|
51
|
+
/**
|
|
52
|
+
* Merge groups of similar artifacts (cosine similarity >= threshold) into single
|
|
53
|
+
* combined artifacts to reduce redundancy without discarding information entirely.
|
|
54
|
+
*/
|
|
55
|
+
microCompact(artifacts: ContextArtifact[], similarityThreshold?: number): ContextArtifact[];
|
|
56
|
+
/**
|
|
57
|
+
* Automatically compact a pack to fit within hardLimitTokens.
|
|
58
|
+
* First applies microCompact (lossless merging), then compact (trimming by relevance).
|
|
59
|
+
*/
|
|
60
|
+
autoCompact(pack: ContextPack, hardLimitTokens: number): ContextPack;
|
|
46
61
|
/**
|
|
47
62
|
* Filter artifacts to those whose path-like tags are within mutation_scope.
|
|
48
63
|
* L0/L1 tiers apply the filter; L2/L3 skip it (full access).
|
|
@@ -159,6 +159,84 @@ class ContextPackBuilder {
|
|
|
159
159
|
buildLightweight(workItem, state, lessons) {
|
|
160
160
|
return this.build(workItem, state, [], new Map(), lessons);
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Remove artifacts with lowest relevance until the pack fits within targetTokens.
|
|
164
|
+
* Artifacts already sorted by relevance; we trim the tail.
|
|
165
|
+
*/
|
|
166
|
+
compact(pack, targetTokens) {
|
|
167
|
+
if (estimateTokens(pack.artifacts.map((a) => a.content).join('\n')) <= targetTokens) {
|
|
168
|
+
return pack;
|
|
169
|
+
}
|
|
170
|
+
const sorted = [...pack.artifacts].sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
171
|
+
const trimmed = [];
|
|
172
|
+
let used = 0;
|
|
173
|
+
for (const artifact of sorted) {
|
|
174
|
+
const t = estimateTokens(artifact.content);
|
|
175
|
+
if (used + t > targetTokens)
|
|
176
|
+
break;
|
|
177
|
+
trimmed.push(artifact);
|
|
178
|
+
used += t;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
...pack,
|
|
182
|
+
artifacts: trimmed,
|
|
183
|
+
redundancy_removed: pack.redundancy_removed + (pack.artifacts.length - trimmed.length),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Merge groups of similar artifacts (cosine similarity >= threshold) into single
|
|
188
|
+
* combined artifacts to reduce redundancy without discarding information entirely.
|
|
189
|
+
*/
|
|
190
|
+
microCompact(artifacts, similarityThreshold = 0.7) {
|
|
191
|
+
const merged = [];
|
|
192
|
+
const used = new Set();
|
|
193
|
+
for (let i = 0; i < artifacts.length; i++) {
|
|
194
|
+
if (used.has(i))
|
|
195
|
+
continue;
|
|
196
|
+
const group = [artifacts[i]];
|
|
197
|
+
for (let j = i + 1; j < artifacts.length; j++) {
|
|
198
|
+
if (!used.has(j) && cosineSimilarity(artifacts[i], artifacts[j]) >= similarityThreshold) {
|
|
199
|
+
group.push(artifacts[j]);
|
|
200
|
+
used.add(j);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
used.add(i);
|
|
204
|
+
if (group.length === 1) {
|
|
205
|
+
merged.push(group[0]);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const combinedContent = group
|
|
209
|
+
.map((a) => a.content)
|
|
210
|
+
.join('\n---\n')
|
|
211
|
+
.slice(0, 4000);
|
|
212
|
+
merged.push({
|
|
213
|
+
id: group[0].id,
|
|
214
|
+
kind: group[0].kind,
|
|
215
|
+
content: combinedContent,
|
|
216
|
+
relevanceScore: group.reduce((s, a) => s + a.relevanceScore, 0) / group.length,
|
|
217
|
+
tags: [...new Set(group.flatMap((a) => a.tags))],
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return merged;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Automatically compact a pack to fit within hardLimitTokens.
|
|
225
|
+
* First applies microCompact (lossless merging), then compact (trimming by relevance).
|
|
226
|
+
*/
|
|
227
|
+
autoCompact(pack, hardLimitTokens) {
|
|
228
|
+
const currentTokens = estimateTokens(pack.artifacts.map((a) => a.content).join('\n'));
|
|
229
|
+
if (currentTokens <= hardLimitTokens)
|
|
230
|
+
return pack;
|
|
231
|
+
const microCompacted = this.microCompact(pack.artifacts);
|
|
232
|
+
const removedByMicro = pack.artifacts.length - microCompacted.length;
|
|
233
|
+
const interim = {
|
|
234
|
+
...pack,
|
|
235
|
+
artifacts: microCompacted,
|
|
236
|
+
redundancy_removed: pack.redundancy_removed + removedByMicro,
|
|
237
|
+
};
|
|
238
|
+
return this.compact(interim, hardLimitTokens);
|
|
239
|
+
}
|
|
162
240
|
/**
|
|
163
241
|
* Filter artifacts to those whose path-like tags are within mutation_scope.
|
|
164
242
|
* L0/L1 tiers apply the filter; L2/L3 skip it (full access).
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const EVENT_TYPES: readonly ["SessionCreated", "RunStarted", "GraphCompiled", "WorkItemReady", "WorkspaceAllocated", "AttemptStarted", "ToolInvoked", "ToolCompleted", "ToolFailed", "EvidenceCollected", "PolicyEvaluated", "GateRequested", "GateResolved", "VerificationStarted", "VerificationCompleted", "RetryScheduled", "WorkItemCompleted", "WorkItemBlocked", "RunCompleted", "RetroPublished", "LessonPromoted"];
|
|
1
|
+
export declare const EVENT_TYPES: readonly ["SessionCreated", "RunStarted", "GraphCompiled", "WorkItemReady", "WorkspaceAllocated", "AttemptStarted", "ToolInvoked", "ToolCompleted", "ToolFailed", "EvidenceCollected", "PolicyEvaluated", "GateRequested", "GateResolved", "VerificationStarted", "VerificationCompleted", "RetryScheduled", "WorkItemCompleted", "WorkItemBlocked", "RunCompleted", "RetroPublished", "LessonPromoted", "RunAborted", "RollbackExecuted", "RollbackFailed", "TaskErrorBoundaryTripped", "WorkspaceDisposeFailed"];
|
|
2
2
|
export type EventType = (typeof EVENT_TYPES)[number];
|
|
3
3
|
export declare function isValidEventType(type: string): type is EventType;
|
|
@@ -24,6 +24,11 @@ exports.EVENT_TYPES = [
|
|
|
24
24
|
'RunCompleted',
|
|
25
25
|
'RetroPublished',
|
|
26
26
|
'LessonPromoted',
|
|
27
|
+
'RunAborted',
|
|
28
|
+
'RollbackExecuted',
|
|
29
|
+
'RollbackFailed',
|
|
30
|
+
'TaskErrorBoundaryTripped',
|
|
31
|
+
'WorkspaceDisposeFailed',
|
|
27
32
|
];
|
|
28
33
|
function isValidEventType(type) {
|
|
29
34
|
return exports.EVENT_TYPES.includes(type);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectToolsForActions = selectToolsForActions;
|
|
4
|
+
const built_in_tools_1 = require("./built-in-tools");
|
|
5
|
+
const READ_TOOLS = [
|
|
6
|
+
built_in_tools_1.BUILT_IN_TOOLS.read_file.schema,
|
|
7
|
+
built_in_tools_1.BUILT_IN_TOOLS.glob.schema,
|
|
8
|
+
built_in_tools_1.BUILT_IN_TOOLS.grep.schema,
|
|
9
|
+
];
|
|
10
|
+
const PATCH_TOOLS = [
|
|
11
|
+
built_in_tools_1.BUILT_IN_TOOLS.read_file.schema,
|
|
12
|
+
built_in_tools_1.BUILT_IN_TOOLS.write_file.schema,
|
|
13
|
+
built_in_tools_1.BUILT_IN_TOOLS.patch_file.schema,
|
|
14
|
+
];
|
|
15
|
+
const RUN_TOOLS = [built_in_tools_1.BUILT_IN_TOOLS.run_command.schema];
|
|
16
|
+
const EVIDENCE_TOOLS = [
|
|
17
|
+
built_in_tools_1.BUILT_IN_TOOLS.read_file.schema,
|
|
18
|
+
built_in_tools_1.BUILT_IN_TOOLS.glob.schema,
|
|
19
|
+
built_in_tools_1.BUILT_IN_TOOLS.run_command.schema,
|
|
20
|
+
];
|
|
21
|
+
const ACTION_TOOL_MAP = {
|
|
22
|
+
read_code: READ_TOOLS,
|
|
23
|
+
generate_patch: PATCH_TOOLS,
|
|
24
|
+
run_tests: RUN_TOOLS,
|
|
25
|
+
run_lint: RUN_TOOLS,
|
|
26
|
+
collect_evidence: EVIDENCE_TOOLS,
|
|
27
|
+
custom: built_in_tools_1.ALL_BUILT_IN_SCHEMAS,
|
|
28
|
+
};
|
|
29
|
+
function selectToolsForActions(actions) {
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const result = [];
|
|
32
|
+
for (const action of actions) {
|
|
33
|
+
for (const tool of ACTION_TOOL_MAP[action.type] ?? built_in_tools_1.ALL_BUILT_IN_SCHEMAS) {
|
|
34
|
+
if (!seen.has(tool.function.name)) {
|
|
35
|
+
seen.add(tool.function.name);
|
|
36
|
+
result.push(tool);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolSchema } from './stream-completion';
|
|
2
|
+
export interface BuiltInToolHandler {
|
|
3
|
+
schema: ToolSchema;
|
|
4
|
+
idempotent: boolean;
|
|
5
|
+
execute(args: Record<string, unknown>, cwd: string): Promise<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare const BUILT_IN_TOOLS: Record<string, BuiltInToolHandler>;
|
|
8
|
+
export declare const ALL_BUILT_IN_SCHEMAS: ToolSchema[];
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ALL_BUILT_IN_SCHEMAS = exports.BUILT_IN_TOOLS = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
// ─── read_file ────────────────────────────────────────────────────────────────
|
|
11
|
+
const readFile = {
|
|
12
|
+
idempotent: true,
|
|
13
|
+
schema: {
|
|
14
|
+
type: 'function',
|
|
15
|
+
function: {
|
|
16
|
+
name: 'read_file',
|
|
17
|
+
description: 'Read the contents of a file',
|
|
18
|
+
parameters: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
path: { type: 'string', description: 'File path relative to workspace root' },
|
|
22
|
+
offset: { type: 'integer', description: 'Line to start reading from (1-based)' },
|
|
23
|
+
limit: { type: 'integer', description: 'Maximum number of lines to read' },
|
|
24
|
+
},
|
|
25
|
+
required: ['path'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
async execute(args, cwd) {
|
|
30
|
+
const filePath = path_1.default.resolve(cwd, String(args.path));
|
|
31
|
+
if (!fs_1.default.existsSync(filePath))
|
|
32
|
+
return `Error: file not found: ${args.path}`;
|
|
33
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
34
|
+
const lines = content.split('\n');
|
|
35
|
+
const offset = typeof args.offset === 'number' ? args.offset - 1 : 0;
|
|
36
|
+
const limit = typeof args.limit === 'number' ? args.limit : lines.length;
|
|
37
|
+
return lines.slice(offset, offset + limit).join('\n');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// ─── write_file ───────────────────────────────────────────────────────────────
|
|
41
|
+
const writeFile = {
|
|
42
|
+
idempotent: false,
|
|
43
|
+
schema: {
|
|
44
|
+
type: 'function',
|
|
45
|
+
function: {
|
|
46
|
+
name: 'write_file',
|
|
47
|
+
description: 'Write content to a file (creates or overwrites)',
|
|
48
|
+
parameters: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
path: { type: 'string', description: 'File path relative to workspace root' },
|
|
52
|
+
content: { type: 'string', description: 'Content to write' },
|
|
53
|
+
},
|
|
54
|
+
required: ['path', 'content'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async execute(args, cwd) {
|
|
59
|
+
const filePath = path_1.default.resolve(cwd, String(args.path));
|
|
60
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
61
|
+
fs_1.default.writeFileSync(filePath, String(args.content), 'utf8');
|
|
62
|
+
return `Written ${String(args.content).length} bytes to ${args.path}`;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
// ─── patch_file ───────────────────────────────────────────────────────────────
|
|
66
|
+
const patchFile = {
|
|
67
|
+
idempotent: false,
|
|
68
|
+
schema: {
|
|
69
|
+
type: 'function',
|
|
70
|
+
function: {
|
|
71
|
+
name: 'patch_file',
|
|
72
|
+
description: 'Replace an exact string in a file with new content',
|
|
73
|
+
parameters: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
path: { type: 'string', description: 'File path relative to workspace root' },
|
|
77
|
+
old_string: { type: 'string', description: 'Exact string to replace' },
|
|
78
|
+
new_string: { type: 'string', description: 'Replacement string' },
|
|
79
|
+
},
|
|
80
|
+
required: ['path', 'old_string', 'new_string'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
async execute(args, cwd) {
|
|
85
|
+
const filePath = path_1.default.resolve(cwd, String(args.path));
|
|
86
|
+
if (!fs_1.default.existsSync(filePath))
|
|
87
|
+
return `Error: file not found: ${args.path}`;
|
|
88
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
89
|
+
const oldStr = String(args.old_string);
|
|
90
|
+
if (!content.includes(oldStr))
|
|
91
|
+
return `Error: old_string not found in ${args.path}`;
|
|
92
|
+
const updated = content.replace(oldStr, String(args.new_string));
|
|
93
|
+
fs_1.default.writeFileSync(filePath, updated, 'utf8');
|
|
94
|
+
return `Patched ${args.path}`;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
// ─── glob ─────────────────────────────────────────────────────────────────────
|
|
98
|
+
const glob = {
|
|
99
|
+
idempotent: true,
|
|
100
|
+
schema: {
|
|
101
|
+
type: 'function',
|
|
102
|
+
function: {
|
|
103
|
+
name: 'glob',
|
|
104
|
+
description: 'List files matching a glob pattern within the workspace',
|
|
105
|
+
parameters: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
pattern: { type: 'string', description: 'Glob pattern, e.g. src/**/*.ts' },
|
|
109
|
+
},
|
|
110
|
+
required: ['pattern'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
async execute(args, cwd) {
|
|
115
|
+
const results = globSync(String(args.pattern), cwd);
|
|
116
|
+
return results.length ? results.join('\n') : '(no files matched)';
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
function globSync(pattern, root) {
|
|
120
|
+
const results = [];
|
|
121
|
+
const parts = pattern.split('/');
|
|
122
|
+
function walk(dir, remaining) {
|
|
123
|
+
if (!fs_1.default.existsSync(dir))
|
|
124
|
+
return;
|
|
125
|
+
const [head, ...tail] = remaining;
|
|
126
|
+
if (!head)
|
|
127
|
+
return;
|
|
128
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const matches = micromatch(entry.name, head);
|
|
131
|
+
if (!matches)
|
|
132
|
+
continue;
|
|
133
|
+
const full = path_1.default.join(dir, entry.name);
|
|
134
|
+
if (tail.length === 0) {
|
|
135
|
+
results.push(path_1.default.relative(root, full).replace(/\\/g, '/'));
|
|
136
|
+
}
|
|
137
|
+
else if (entry.isDirectory()) {
|
|
138
|
+
if (head === '**') {
|
|
139
|
+
walk(full, remaining);
|
|
140
|
+
walk(full, tail);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
walk(full, tail);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
walk(root, parts);
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
function micromatch(name, pattern) {
|
|
152
|
+
if (pattern === '**')
|
|
153
|
+
return true;
|
|
154
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '[^/]*').replace(/\?/g, '[^/]') + '$');
|
|
155
|
+
return regex.test(name);
|
|
156
|
+
}
|
|
157
|
+
// ─── grep ─────────────────────────────────────────────────────────────────────
|
|
158
|
+
const grep = {
|
|
159
|
+
idempotent: true,
|
|
160
|
+
schema: {
|
|
161
|
+
type: 'function',
|
|
162
|
+
function: {
|
|
163
|
+
name: 'grep',
|
|
164
|
+
description: 'Search for a regex pattern in files',
|
|
165
|
+
parameters: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
pattern: { type: 'string', description: 'Regex pattern to search for' },
|
|
169
|
+
path: { type: 'string', description: 'File or directory to search in (relative to workspace)' },
|
|
170
|
+
},
|
|
171
|
+
required: ['pattern'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
async execute(args, cwd) {
|
|
176
|
+
const searchRoot = args.path ? path_1.default.resolve(cwd, String(args.path)) : cwd;
|
|
177
|
+
const regex = new RegExp(String(args.pattern));
|
|
178
|
+
const results = [];
|
|
179
|
+
grepDir(searchRoot, regex, cwd, results);
|
|
180
|
+
return results.length ? results.slice(0, 200).join('\n') : '(no matches)';
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
function grepDir(dir, regex, root, out) {
|
|
184
|
+
if (!fs_1.default.existsSync(dir))
|
|
185
|
+
return;
|
|
186
|
+
const stat = fs_1.default.statSync(dir);
|
|
187
|
+
if (stat.isFile()) {
|
|
188
|
+
grepFile(dir, regex, root, out);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
192
|
+
const full = path_1.default.join(dir, entry.name);
|
|
193
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
194
|
+
grepDir(full, regex, root, out);
|
|
195
|
+
}
|
|
196
|
+
else if (entry.isFile()) {
|
|
197
|
+
grepFile(full, regex, root, out);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function grepFile(filePath, regex, root, out) {
|
|
202
|
+
try {
|
|
203
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
204
|
+
content.split('\n').forEach((line, i) => {
|
|
205
|
+
if (regex.test(line)) {
|
|
206
|
+
out.push(`${path_1.default.relative(root, filePath).replace(/\\/g, '/')}:${i + 1}: ${line}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch { /* skip binary or unreadable files */ }
|
|
211
|
+
}
|
|
212
|
+
// ─── run_command ──────────────────────────────────────────────────────────────
|
|
213
|
+
const runCommand = {
|
|
214
|
+
idempotent: false,
|
|
215
|
+
schema: {
|
|
216
|
+
type: 'function',
|
|
217
|
+
function: {
|
|
218
|
+
name: 'run_command',
|
|
219
|
+
description: 'Run a shell command in the workspace directory',
|
|
220
|
+
parameters: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
command: { type: 'string', description: 'Shell command to execute' },
|
|
224
|
+
timeout_ms: { type: 'integer', description: 'Timeout in milliseconds (default 30000)' },
|
|
225
|
+
},
|
|
226
|
+
required: ['command'],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
async execute(args, cwd) {
|
|
231
|
+
const command = String(args.command);
|
|
232
|
+
const timeoutMs = typeof args.timeout_ms === 'number' ? args.timeout_ms : 30000;
|
|
233
|
+
return runShell(command, cwd, timeoutMs);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
function runShell(command, cwd, timeoutMs) {
|
|
237
|
+
return new Promise((resolve) => {
|
|
238
|
+
const isWin = process.platform === 'win32';
|
|
239
|
+
const shell = isWin ? 'cmd' : 'sh';
|
|
240
|
+
const shellArgs = isWin ? ['/c', command] : ['-c', command];
|
|
241
|
+
const proc = (0, child_process_1.spawn)(shell, shellArgs, { cwd, stdio: 'pipe' });
|
|
242
|
+
const out = [];
|
|
243
|
+
let timedOut = false;
|
|
244
|
+
const timer = setTimeout(() => { timedOut = true; proc.kill('SIGTERM'); }, timeoutMs);
|
|
245
|
+
proc.stdout.on('data', (c) => out.push(c));
|
|
246
|
+
proc.stderr.on('data', (c) => out.push(c));
|
|
247
|
+
proc.on('close', (code) => {
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
const text = Buffer.concat(out).toString('utf8').trim();
|
|
250
|
+
if (timedOut)
|
|
251
|
+
resolve(`[timed out after ${timeoutMs}ms]\n${text}`);
|
|
252
|
+
else
|
|
253
|
+
resolve(code === 0 ? text || '(no output)' : `[exit ${code}]\n${text}`);
|
|
254
|
+
});
|
|
255
|
+
proc.on('error', (err) => { clearTimeout(timer); resolve(`[error] ${err}`); });
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
259
|
+
exports.BUILT_IN_TOOLS = {
|
|
260
|
+
read_file: readFile,
|
|
261
|
+
write_file: writeFile,
|
|
262
|
+
patch_file: patchFile,
|
|
263
|
+
glob,
|
|
264
|
+
grep,
|
|
265
|
+
run_command: runCommand,
|
|
266
|
+
};
|
|
267
|
+
exports.ALL_BUILT_IN_SCHEMAS = Object.values(exports.BUILT_IN_TOOLS).map((t) => t.schema);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { LlmTaskExecutor } from './llm-task-executor';
|
|
2
|
+
export type { LlmProviderConfig, LlmExecutorEvent, LlmExecutorEventType } from './llm-task-executor';
|
|
3
|
+
export { buildNodePrompt } from './node-prompt-builder';
|
|
4
|
+
export { selectToolsForActions } from './action-tool-map';
|
|
5
|
+
export { BUILT_IN_TOOLS, ALL_BUILT_IN_SCHEMAS } from './built-in-tools';
|
|
6
|
+
export type { BuiltInToolHandler } from './built-in-tools';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALL_BUILT_IN_SCHEMAS = exports.BUILT_IN_TOOLS = exports.selectToolsForActions = exports.buildNodePrompt = exports.LlmTaskExecutor = void 0;
|
|
4
|
+
var llm_task_executor_1 = require("./llm-task-executor");
|
|
5
|
+
Object.defineProperty(exports, "LlmTaskExecutor", { enumerable: true, get: function () { return llm_task_executor_1.LlmTaskExecutor; } });
|
|
6
|
+
var node_prompt_builder_1 = require("./node-prompt-builder");
|
|
7
|
+
Object.defineProperty(exports, "buildNodePrompt", { enumerable: true, get: function () { return node_prompt_builder_1.buildNodePrompt; } });
|
|
8
|
+
var action_tool_map_1 = require("./action-tool-map");
|
|
9
|
+
Object.defineProperty(exports, "selectToolsForActions", { enumerable: true, get: function () { return action_tool_map_1.selectToolsForActions; } });
|
|
10
|
+
var built_in_tools_1 = require("./built-in-tools");
|
|
11
|
+
Object.defineProperty(exports, "BUILT_IN_TOOLS", { enumerable: true, get: function () { return built_in_tools_1.BUILT_IN_TOOLS; } });
|
|
12
|
+
Object.defineProperty(exports, "ALL_BUILT_IN_SCHEMAS", { enumerable: true, get: function () { return built_in_tools_1.ALL_BUILT_IN_SCHEMAS; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { GraphNode } from '../compiler/graph-compiler';
|
|
2
|
+
import type { WorkspaceLease } from '../models/workspace';
|
|
3
|
+
import type { TaskExecutor, TaskResult } from '../scheduler/scheduler';
|
|
4
|
+
import type { PluginRegistry } from '../plugins/plugin-registry';
|
|
5
|
+
export interface LlmProviderConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
model: string;
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
maxTurns?: number;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
systemPrompt?: string;
|
|
13
|
+
}
|
|
14
|
+
export type LlmExecutorEventType = 'turn_start' | 'turn_complete' | 'tool_call' | 'tool_result' | 'executor_complete';
|
|
15
|
+
export interface LlmExecutorEvent {
|
|
16
|
+
type: LlmExecutorEventType;
|
|
17
|
+
nodeId: string;
|
|
18
|
+
attempt: number;
|
|
19
|
+
detail?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export declare class LlmTaskExecutor implements TaskExecutor {
|
|
22
|
+
private readonly provider;
|
|
23
|
+
private readonly registry?;
|
|
24
|
+
private readonly onProgress?;
|
|
25
|
+
constructor(provider: LlmProviderConfig, registry?: PluginRegistry | undefined, onProgress?: ((event: LlmExecutorEvent) => void) | undefined);
|
|
26
|
+
execute(node: GraphNode, lease: WorkspaceLease, runId: string, attempt: number): Promise<TaskResult>;
|
|
27
|
+
private invokeToolCall;
|
|
28
|
+
private emit;
|
|
29
|
+
}
|