omegon 0.6.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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-log — Append-only session tracking
|
|
3
|
+
*
|
|
4
|
+
* Registers `/session-log` command that appends a structured entry to
|
|
5
|
+
* the .session_log file in the repository root (or reads existing entries).
|
|
6
|
+
*
|
|
7
|
+
* Subcommands:
|
|
8
|
+
* /session-log — Generate and append a new entry for this session
|
|
9
|
+
* /session-log read — Show recent entries
|
|
10
|
+
* /session-log read <n> — Show last n entries
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, basename } from "node:path";
|
|
15
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
16
|
+
|
|
17
|
+
const SESSION_LOG_HEADER = `# Session Log
|
|
18
|
+
|
|
19
|
+
Append-only record of development sessions. Read recent entries for context.
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export default function sessionLogExtension(pi: ExtensionAPI) {
|
|
23
|
+
|
|
24
|
+
// ------------------------------------------------------------------
|
|
25
|
+
// Auto-read session log on session start for context
|
|
26
|
+
// ------------------------------------------------------------------
|
|
27
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
28
|
+
const logPath = join(ctx.cwd, ".session_log");
|
|
29
|
+
if (existsSync(logPath)) {
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(logPath, "utf-8");
|
|
32
|
+
const lines = content.split("\n");
|
|
33
|
+
// Show last ~80 lines as context (lightweight, non-intrusive)
|
|
34
|
+
const tail = lines.slice(-80).join("\n").trim();
|
|
35
|
+
if (tail) {
|
|
36
|
+
// Inject as a context message so the LLM knows recent history
|
|
37
|
+
pi.sendMessage({
|
|
38
|
+
customType: "session-log-context",
|
|
39
|
+
content: `Recent .session_log entries (last 80 lines):\n\n${tail}`,
|
|
40
|
+
display: false, // Don't clutter the user's display
|
|
41
|
+
}, { deliverAs: "nextTurn" });
|
|
42
|
+
}
|
|
43
|
+
} catch { /* ignore read errors */ }
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ------------------------------------------------------------------
|
|
48
|
+
// /session-log command
|
|
49
|
+
// ------------------------------------------------------------------
|
|
50
|
+
pi.registerCommand("session-log", {
|
|
51
|
+
description: "Append or read .session_log entries (usage: /session-log [read [n]])",
|
|
52
|
+
getArgumentCompletions: (prefix: string) => {
|
|
53
|
+
const items = [
|
|
54
|
+
{ value: "read", label: "read", description: "Show recent entries" },
|
|
55
|
+
];
|
|
56
|
+
const filtered = items.filter((i) => i.value.startsWith(prefix || ""));
|
|
57
|
+
return filtered.length > 0 ? filtered : null;
|
|
58
|
+
},
|
|
59
|
+
handler: async (args, ctx) => {
|
|
60
|
+
const trimmed = (args || "").trim();
|
|
61
|
+
const cwd = ctx.cwd;
|
|
62
|
+
const logPath = join(cwd, ".session_log");
|
|
63
|
+
|
|
64
|
+
// ----------------------------------------------------------
|
|
65
|
+
// /session-log read [n]
|
|
66
|
+
// ----------------------------------------------------------
|
|
67
|
+
if (trimmed.startsWith("read")) {
|
|
68
|
+
if (!existsSync(logPath)) {
|
|
69
|
+
pi.sendMessage({
|
|
70
|
+
customType: "view",
|
|
71
|
+
content: `No .session_log found at \`${logPath}\``,
|
|
72
|
+
display: true,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const content = readFileSync(logPath, "utf-8");
|
|
78
|
+
const nArg = trimmed.replace(/^read\s*/, "").trim();
|
|
79
|
+
const n = nArg ? parseInt(nArg, 10) : 5;
|
|
80
|
+
|
|
81
|
+
// Split into entries by ## headings
|
|
82
|
+
const entries = content.split(/^(?=## \d{4}-\d{2}-\d{2})/m);
|
|
83
|
+
const header = entries[0]?.startsWith("#") && !entries[0]?.startsWith("## 2") ? entries.shift() : "";
|
|
84
|
+
const recent = entries.slice(-n);
|
|
85
|
+
|
|
86
|
+
if (recent.length === 0) {
|
|
87
|
+
pi.sendMessage({
|
|
88
|
+
customType: "view",
|
|
89
|
+
content: "No entries found in `.session_log`",
|
|
90
|
+
display: true,
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const display = recent.join("\n").trim();
|
|
96
|
+
pi.sendMessage({
|
|
97
|
+
customType: "view",
|
|
98
|
+
content: `**Recent .session_log entries** (${recent.length} of ${entries.length}):\n\n${display}`,
|
|
99
|
+
display: true,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ----------------------------------------------------------
|
|
105
|
+
// /session-log (generate + append)
|
|
106
|
+
// ----------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
// Get today's date
|
|
109
|
+
let today = new Date().toISOString().slice(0, 10);
|
|
110
|
+
try {
|
|
111
|
+
const dateResult = await pi.exec("date", ["+%Y-%m-%d"], { timeout: 3_000 });
|
|
112
|
+
today = dateResult.stdout.trim() || today;
|
|
113
|
+
} catch { /* use JS date */ }
|
|
114
|
+
|
|
115
|
+
// Get git info for context
|
|
116
|
+
let branch = "";
|
|
117
|
+
let recentCommits = "";
|
|
118
|
+
try {
|
|
119
|
+
const branchResult = await pi.exec("git", ["branch", "--show-current"], { timeout: 5_000, cwd });
|
|
120
|
+
branch = branchResult.stdout.trim();
|
|
121
|
+
} catch { /* ignore */ }
|
|
122
|
+
try {
|
|
123
|
+
const logResult = await pi.exec(
|
|
124
|
+
"git", ["log", "--oneline", "-5", "--no-decorate"],
|
|
125
|
+
{ timeout: 5_000, cwd },
|
|
126
|
+
);
|
|
127
|
+
recentCommits = logResult.stdout.trim();
|
|
128
|
+
} catch { /* ignore */ }
|
|
129
|
+
|
|
130
|
+
// Bootstrap .session_log if it doesn't exist
|
|
131
|
+
const logExists = existsSync(logPath);
|
|
132
|
+
|
|
133
|
+
// Build the prompt for the LLM to generate and append the entry
|
|
134
|
+
const prompt = [
|
|
135
|
+
`Analyze the conversation context and append a session log entry to \`${logPath}\`.`,
|
|
136
|
+
"",
|
|
137
|
+
logExists
|
|
138
|
+
? "The file already exists — append to the end."
|
|
139
|
+
: `The file does not exist yet. Create it with this header first:\n\n\`\`\`\n${SESSION_LOG_HEADER}\`\`\`\n\nThen append the entry.`,
|
|
140
|
+
"",
|
|
141
|
+
`**Today's date:** ${today}`,
|
|
142
|
+
branch ? `**Branch:** ${branch}` : "",
|
|
143
|
+
recentCommits ? `**Recent commits:**\n\`\`\`\n${recentCommits}\n\`\`\`` : "",
|
|
144
|
+
"",
|
|
145
|
+
"**Entry format** (use exactly this structure):",
|
|
146
|
+
"",
|
|
147
|
+
`\`\`\`markdown`,
|
|
148
|
+
`## ${today} — Brief Topic Title`,
|
|
149
|
+
"",
|
|
150
|
+
"### Context",
|
|
151
|
+
"One-line description of what prompted this session.",
|
|
152
|
+
"",
|
|
153
|
+
"### Completed",
|
|
154
|
+
"- Concise bullet points of what was accomplished",
|
|
155
|
+
"- Focus on outcomes, not process",
|
|
156
|
+
"",
|
|
157
|
+
"### Decisions",
|
|
158
|
+
"- Choices made and brief rationale",
|
|
159
|
+
"",
|
|
160
|
+
"### Open Threads",
|
|
161
|
+
"- Unfinished work for future sessions",
|
|
162
|
+
"- Known issues discovered",
|
|
163
|
+
"```",
|
|
164
|
+
"",
|
|
165
|
+
"**Guidelines:**",
|
|
166
|
+
"- Keep entries concise — they should trigger memory, not replace documentation",
|
|
167
|
+
"- Focus on outcomes, decisions, and open threads",
|
|
168
|
+
"- Do NOT overwrite or edit previous entries",
|
|
169
|
+
].filter(Boolean).join("\n");
|
|
170
|
+
|
|
171
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared state between extensions loaded in the same pi process.
|
|
3
|
+
*
|
|
4
|
+
* Uses globalThis to guarantee sharing regardless of module loader
|
|
5
|
+
* caching behavior (jiti may create separate instances per extension).
|
|
6
|
+
*
|
|
7
|
+
* Keep this minimal — only data that genuinely needs cross-extension sharing.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
DesignTreeDashboardState,
|
|
12
|
+
OpenSpecDashboardState,
|
|
13
|
+
CleaveState,
|
|
14
|
+
RecoveryDashboardState,
|
|
15
|
+
} from "./dashboard/types.ts";
|
|
16
|
+
|
|
17
|
+
import type { EffortState } from "./effort/types.ts";
|
|
18
|
+
import type { ProviderRoutingPolicy } from "./lib/model-routing.ts";
|
|
19
|
+
import type { MemoryInjectionMetrics } from "./project-memory/injection-metrics.ts";
|
|
20
|
+
import type { LifecycleMemoryMessage } from "./project-memory/types.ts";
|
|
21
|
+
import { getDefaultPolicy } from "./lib/model-routing.ts";
|
|
22
|
+
|
|
23
|
+
export type RecoveryFailureClassification =
|
|
24
|
+
| "transient_server_error"
|
|
25
|
+
| "rate_limited"
|
|
26
|
+
| "quota_exhausted"
|
|
27
|
+
| "authentication_failed"
|
|
28
|
+
| "malformed_output"
|
|
29
|
+
| "context_overflow"
|
|
30
|
+
| "invalid_request"
|
|
31
|
+
| "unknown_upstream";
|
|
32
|
+
|
|
33
|
+
export type RecoveryDisposition =
|
|
34
|
+
| "retry_same_model"
|
|
35
|
+
| "cooldown_and_failover"
|
|
36
|
+
| "guidance_only"
|
|
37
|
+
| "handled_elsewhere"
|
|
38
|
+
| "escalate";
|
|
39
|
+
|
|
40
|
+
export interface RecoveryEvent {
|
|
41
|
+
provider: string;
|
|
42
|
+
model: string;
|
|
43
|
+
turnIndex: number;
|
|
44
|
+
classification: RecoveryFailureClassification;
|
|
45
|
+
originalErrorSummary: string;
|
|
46
|
+
retryable: boolean;
|
|
47
|
+
disposition: RecoveryDisposition;
|
|
48
|
+
retryAttempted: boolean;
|
|
49
|
+
retryCount: number;
|
|
50
|
+
maxRetries: number;
|
|
51
|
+
guidance: string;
|
|
52
|
+
cooldownApplied?: boolean;
|
|
53
|
+
alternateCandidate?: {
|
|
54
|
+
provider: string;
|
|
55
|
+
model: string;
|
|
56
|
+
};
|
|
57
|
+
timestamp: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Re-export dashboard types for consumer convenience
|
|
61
|
+
export type {
|
|
62
|
+
DesignTreeDashboardState,
|
|
63
|
+
DesignTreeFocusedNode,
|
|
64
|
+
OpenSpecDashboardState,
|
|
65
|
+
OpenSpecChangeEntry,
|
|
66
|
+
CleaveState,
|
|
67
|
+
CleaveChildState,
|
|
68
|
+
CleaveStatus,
|
|
69
|
+
DashboardMode,
|
|
70
|
+
DashboardState,
|
|
71
|
+
} from "./dashboard/types.ts";
|
|
72
|
+
|
|
73
|
+
// Re-export routing types for consumer convenience
|
|
74
|
+
export type { ProviderRoutingPolicy, ResolvedTierModel, ModelTier, ProviderName } from "./lib/model-routing.ts";
|
|
75
|
+
|
|
76
|
+
// Re-export effort types for consumer convenience
|
|
77
|
+
export type {
|
|
78
|
+
EffortState,
|
|
79
|
+
EffortConfig,
|
|
80
|
+
EffortLevel,
|
|
81
|
+
EffortModelTier,
|
|
82
|
+
EffortName,
|
|
83
|
+
ThinkingLevel,
|
|
84
|
+
} from "./effort/types.ts";
|
|
85
|
+
|
|
86
|
+
/** Event channel fired by producers after writing dashboard state. */
|
|
87
|
+
export const DASHBOARD_UPDATE_EVENT = "dashboard:update" as const;
|
|
88
|
+
|
|
89
|
+
const SHARED_KEY = Symbol.for("pi-kit-shared-state");
|
|
90
|
+
|
|
91
|
+
interface SharedState {
|
|
92
|
+
/** Approximate token count of the last memory injection into context.
|
|
93
|
+
* Written by project-memory, read by dashboard for the context gauge. */
|
|
94
|
+
memoryTokenEstimate: number;
|
|
95
|
+
|
|
96
|
+
/** Structured snapshot of the last memory injection payload. */
|
|
97
|
+
lastMemoryInjection?: MemoryInjectionMetrics;
|
|
98
|
+
|
|
99
|
+
/** Design tree summary state. Written by design-tree extension. */
|
|
100
|
+
designTree?: DesignTreeDashboardState;
|
|
101
|
+
|
|
102
|
+
/** OpenSpec changes summary. Written by openspec/cleave extension. */
|
|
103
|
+
openspec?: OpenSpecDashboardState;
|
|
104
|
+
|
|
105
|
+
/** Cleave execution state. Written by cleave extension. */
|
|
106
|
+
cleave?: CleaveState;
|
|
107
|
+
|
|
108
|
+
/** Effort tier state. Written by effort extension, read by model-budget and cleave. */
|
|
109
|
+
effort?: EffortState;
|
|
110
|
+
|
|
111
|
+
/** Session routing policy. Written by operator/preflight, read by cleave and model-budget. */
|
|
112
|
+
routingPolicy?: ProviderRoutingPolicy;
|
|
113
|
+
|
|
114
|
+
/** Pending structured lifecycle candidates waiting for project-memory ingestion. */
|
|
115
|
+
lifecycleCandidateQueue?: LifecycleMemoryMessage[];
|
|
116
|
+
|
|
117
|
+
/** Content prefixes of memory facts to archive (e.g. resolved open questions). */
|
|
118
|
+
factArchiveQueue?: string[];
|
|
119
|
+
|
|
120
|
+
/** Latest upstream recovery event for dashboard/harness visibility. */
|
|
121
|
+
latestRecoveryEvent?: RecoveryEvent;
|
|
122
|
+
|
|
123
|
+
/** Dashboard-friendly recovery summary derived from the latest recovery event. */
|
|
124
|
+
recovery?: RecoveryDashboardState;
|
|
125
|
+
|
|
126
|
+
/** Current dashboard display mode. Written by dashboard extension. */
|
|
127
|
+
dashboardMode?: string;
|
|
128
|
+
|
|
129
|
+
/** Number of conversation turns in the current session. Written by dashboard extension. */
|
|
130
|
+
dashboardTurns?: number;
|
|
131
|
+
|
|
132
|
+
/** Per-request retry ledger for bounded recovery decisions across core and extension-driven retries. */
|
|
133
|
+
recoveryRetryCounts?: Record<string, number>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Initialize once on first import, reuse thereafter via global symbol.
|
|
137
|
+
// New dashboard properties are intentionally omitted — they start as undefined
|
|
138
|
+
// and are populated when each producer extension loads.
|
|
139
|
+
if (!(globalThis as any)[SHARED_KEY]) {
|
|
140
|
+
(globalThis as any)[SHARED_KEY] = {
|
|
141
|
+
memoryTokenEstimate: 0,
|
|
142
|
+
routingPolicy: getDefaultPolicy(),
|
|
143
|
+
} satisfies SharedState;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const sharedState: SharedState = (globalThis as any)[SHARED_KEY];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
const verbs = [
|
|
4
|
+
"Communing with the Machine Spirit",
|
|
5
|
+
"Appeasing the Omnissiah",
|
|
6
|
+
"Reciting the Litany of Ignition",
|
|
7
|
+
"Applying sacred unguents",
|
|
8
|
+
"Chanting binharic cant",
|
|
9
|
+
"Performing the Rite of Clear Mind",
|
|
10
|
+
"Querying the Noosphere",
|
|
11
|
+
"Invoking the Motive Force",
|
|
12
|
+
"Beseeching the Machine God",
|
|
13
|
+
"Parsing sacred data-stacks",
|
|
14
|
+
"Administering the Rite of Activation",
|
|
15
|
+
"Placating the logic engine",
|
|
16
|
+
"Consulting the Cult Mechanicus",
|
|
17
|
+
"Interfacing with the cogitator",
|
|
18
|
+
"Calibrating the mechadendrites",
|
|
19
|
+
"Burning sacred incense over the server",
|
|
20
|
+
"Whispering the Cant of Maintenance",
|
|
21
|
+
"Genuflecting before the datacron",
|
|
22
|
+
"Consulting the Oracle at Delphi",
|
|
23
|
+
"Reading the auguries",
|
|
24
|
+
"Descending into the labyrinth",
|
|
25
|
+
"Weaving on Athena's loom",
|
|
26
|
+
"Deciphering the Rosetta Stone",
|
|
27
|
+
"Consulting the Norns",
|
|
28
|
+
"Reading the runes",
|
|
29
|
+
"Forging on Hephaestus's anvil",
|
|
30
|
+
"Navigating the River Styx",
|
|
31
|
+
"Unraveling Ariadne's thread",
|
|
32
|
+
"Stealing fire from Olympus",
|
|
33
|
+
"Divining from the entrails",
|
|
34
|
+
"Reciting the Litany of Hate",
|
|
35
|
+
"Appeasing the Machine Spirit",
|
|
36
|
+
"Performing the Rite of Percussive Maintenance",
|
|
37
|
+
"Purging the heretical code",
|
|
38
|
+
"Suffering not the unclean merge to live",
|
|
39
|
+
"Deploying the Exterminatus on tech debt",
|
|
40
|
+
"Consulting the Codex Astartes",
|
|
41
|
+
"Exorcising the daemon process",
|
|
42
|
+
"Anointing the deployment manifest",
|
|
43
|
+
"Sanctifying the build pipeline",
|
|
44
|
+
"Cataloguing the STC fragments",
|
|
45
|
+
"Venerating the sacred repository",
|
|
46
|
+
"Decrypting the Archaeotech",
|
|
47
|
+
"Supplicating before the Altar of Technology",
|
|
48
|
+
"Conducting the Binary Psalm",
|
|
49
|
+
"Fortifying the firewall bastions",
|
|
50
|
+
"Interrogating the data-looms",
|
|
51
|
+
"Purifying the corrupted sectors",
|
|
52
|
+
"Awakening the dormant forge",
|
|
53
|
+
"Petitioning the Fabricator-General",
|
|
54
|
+
"Scourging the technical debt",
|
|
55
|
+
"Flagellating the failing tests",
|
|
56
|
+
"Martyring the deprecated functions",
|
|
57
|
+
"Crusading through the backlog",
|
|
58
|
+
"Performing battlefield triage on the codebase",
|
|
59
|
+
"Debugging with extreme prejudice",
|
|
60
|
+
"Servicing the servitors",
|
|
61
|
+
"Transcribing the sacred schematics",
|
|
62
|
+
"Committing the Holy Diff",
|
|
63
|
+
"Invoking the Emperor's Protection",
|
|
64
|
+
"Loading the bolter rounds",
|
|
65
|
+
"Administering the Sacred Oils",
|
|
66
|
+
"Propitiating the wrathful forge-spirit",
|
|
67
|
+
"Affixing the Purity Seal to the commit",
|
|
68
|
+
"Performing the Thirteen Rituals of Compilation",
|
|
69
|
+
"Routing the xenos from the dependency tree",
|
|
70
|
+
"Reconsecrating the tainted module",
|
|
71
|
+
"Imploring the Titan's logic core",
|
|
72
|
+
"Soothing the belligerent plasma coil",
|
|
73
|
+
"Undertaking the Pilgrimage to Holy Mars",
|
|
74
|
+
"Grafting the sacred augmetic",
|
|
75
|
+
"Offering a binary prayer to the void",
|
|
76
|
+
"Inscribing the litany upon the circuit board",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
function randomVerb(): string {
|
|
80
|
+
return verbs[Math.floor(Math.random() * verbs.length)] + "...";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default function (pi: ExtensionAPI) {
|
|
84
|
+
pi.on("turn_start", async (_event, ctx) => {
|
|
85
|
+
ctx.ui.setWorkingMessage(randomVerb());
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
pi.on("tool_call", async (_event, ctx) => {
|
|
89
|
+
ctx.ui.setWorkingMessage(randomVerb());
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* style — /style command for the Alpharius design system
|
|
3
|
+
*
|
|
4
|
+
* Registers `/style [subcommand]` as an interactive command.
|
|
5
|
+
* Subcommands: (none), palette, d2, excalidraw, check <file>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Palette data — single source of truth
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
const CORE_PALETTE = {
|
|
15
|
+
primary: "#2ab4c8",
|
|
16
|
+
primaryMuted: "#1a8898",
|
|
17
|
+
primaryBright: "#6ecad8",
|
|
18
|
+
fg: "#c4d8e4",
|
|
19
|
+
mutedFg: "#607888",
|
|
20
|
+
dimFg: "#344858",
|
|
21
|
+
bg: "#06080e",
|
|
22
|
+
cardBg: "#0e1622",
|
|
23
|
+
surfaceBg: "#131e2e",
|
|
24
|
+
borderColor: "#1a3448",
|
|
25
|
+
borderDim: "#0e1e30",
|
|
26
|
+
syntaxType: "#5aa8e0", // sky-blue, distinct from primary teal
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const SIGNALS = {
|
|
30
|
+
green: "#1ab878",
|
|
31
|
+
red: "#c83030",
|
|
32
|
+
orange: "#c86418",
|
|
33
|
+
yellow: "#b89020",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const EXCALIDRAW_SEMANTICS: Record<string, { fill: string; stroke: string; use: string }> = {
|
|
37
|
+
primary: { fill: "#1a4a6e", stroke: "#2ab4c8", use: "Default components, neutral nodes" },
|
|
38
|
+
secondary: { fill: "#1a3a5a", stroke: "#1a8898", use: "Supporting/related components" },
|
|
39
|
+
tertiary: { fill: "#0e2a40", stroke: "#344858", use: "Third-level, background detail" },
|
|
40
|
+
start: { fill: "#0e2e20", stroke: "#1ab878", use: "Entry points, triggers, inputs" },
|
|
41
|
+
end: { fill: "#2e2010", stroke: "#b89020", use: "Outputs, completion, results" },
|
|
42
|
+
decision: { fill: "#2a1010", stroke: "#c83030", use: "Conditionals, branches, choices" },
|
|
43
|
+
ai: { fill: "#1a1040", stroke: "#6060c0", use: "AI/LLM components, inference" },
|
|
44
|
+
warning: { fill: "#2a1808", stroke: "#c86418", use: "Warnings, degraded states" },
|
|
45
|
+
error: { fill: "#2e0e0e", stroke: "#c83030", use: "Error states, failures" },
|
|
46
|
+
evidence: { fill: "#06080e", stroke: "#1a3448", use: "Code snippets, data samples" },
|
|
47
|
+
inactive: { fill: "#0e1622", stroke: "#344858", use: "Disabled, inactive, future-state" },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const D2_STYLE_TEMPLATE = `# Alpharius D2 style template — paste into your .d2 file
|
|
51
|
+
|
|
52
|
+
# Primary component (ceramite blue-teal)
|
|
53
|
+
component: Label {
|
|
54
|
+
style: {
|
|
55
|
+
fill: "#1a4a6e"
|
|
56
|
+
stroke: "#2ab4c8"
|
|
57
|
+
font-color: "#c4d8e4"
|
|
58
|
+
border-radius: 8
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Start / entry point (hydra green)
|
|
63
|
+
entry: Trigger {
|
|
64
|
+
style: {
|
|
65
|
+
fill: "#0e2e20"
|
|
66
|
+
stroke: "#1ab878"
|
|
67
|
+
font-color: "#c4d8e4"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# End / output (brass gold)
|
|
72
|
+
result: Output {
|
|
73
|
+
style: {
|
|
74
|
+
fill: "#2e2010"
|
|
75
|
+
stroke: "#b89020"
|
|
76
|
+
font-color: "#c4d8e4"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Connection
|
|
81
|
+
entry -> component -> result {
|
|
82
|
+
style: {
|
|
83
|
+
stroke: "#2ab4c8"
|
|
84
|
+
font-color: "#c4d8e4"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Container
|
|
89
|
+
group: Infrastructure {
|
|
90
|
+
style: {
|
|
91
|
+
fill: "#06080e"
|
|
92
|
+
stroke: "#1a3448"
|
|
93
|
+
font-color: "#6ecad8"
|
|
94
|
+
}
|
|
95
|
+
}`;
|
|
96
|
+
|
|
97
|
+
// Collect all known hex values for color auditing
|
|
98
|
+
const ALL_TOKENS: Record<string, string> = {
|
|
99
|
+
...CORE_PALETTE,
|
|
100
|
+
...SIGNALS,
|
|
101
|
+
};
|
|
102
|
+
for (const [name, colors] of Object.entries(EXCALIDRAW_SEMANTICS)) {
|
|
103
|
+
ALL_TOKENS[`excalidraw.${name}.fill`] = colors.fill;
|
|
104
|
+
ALL_TOKENS[`excalidraw.${name}.stroke`] = colors.stroke;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Formatters
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
function quickRef(): string {
|
|
112
|
+
return [
|
|
113
|
+
"**Alpharius Design System — Quick Reference**",
|
|
114
|
+
"",
|
|
115
|
+
"```",
|
|
116
|
+
"BACKGROUNDS ACCENTS SIGNALS",
|
|
117
|
+
`bg: ${CORE_PALETTE.bg} primary: ${CORE_PALETTE.primary} green: ${SIGNALS.green}`,
|
|
118
|
+
`cardBg: ${CORE_PALETTE.cardBg} primaryMu: ${CORE_PALETTE.primaryMuted} red: ${SIGNALS.red}`,
|
|
119
|
+
`surfaceBg:${CORE_PALETTE.surfaceBg} primaryBr: ${CORE_PALETTE.primaryBright} orange: ${SIGNALS.orange}`,
|
|
120
|
+
` yellow: ${SIGNALS.yellow}`,
|
|
121
|
+
"",
|
|
122
|
+
"TEXT BORDERS SYNTAX",
|
|
123
|
+
`fg: ${CORE_PALETTE.fg} border: ${CORE_PALETTE.borderColor} syntaxType: ${CORE_PALETTE.syntaxType}`,
|
|
124
|
+
`mutedFg: ${CORE_PALETTE.mutedFg} borderDim: ${CORE_PALETTE.borderDim}`,
|
|
125
|
+
`dimFg: ${CORE_PALETTE.dimFg}`,
|
|
126
|
+
"",
|
|
127
|
+
"EXCALIDRAW SEMANTICS (fill / stroke)",
|
|
128
|
+
`primary: ${EXCALIDRAW_SEMANTICS.primary.fill} / ${EXCALIDRAW_SEMANTICS.primary.stroke} start: ${EXCALIDRAW_SEMANTICS.start.fill} / ${EXCALIDRAW_SEMANTICS.start.stroke}`,
|
|
129
|
+
`secondary: ${EXCALIDRAW_SEMANTICS.secondary.fill} / ${EXCALIDRAW_SEMANTICS.secondary.stroke} end: ${EXCALIDRAW_SEMANTICS.end.fill} / ${EXCALIDRAW_SEMANTICS.end.stroke}`,
|
|
130
|
+
`decision: ${EXCALIDRAW_SEMANTICS.decision.fill} / ${EXCALIDRAW_SEMANTICS.decision.stroke} ai: ${EXCALIDRAW_SEMANTICS.ai.fill} / ${EXCALIDRAW_SEMANTICS.ai.stroke}`,
|
|
131
|
+
`warning: ${EXCALIDRAW_SEMANTICS.warning.fill} / ${EXCALIDRAW_SEMANTICS.warning.stroke} error: ${EXCALIDRAW_SEMANTICS.error.fill} / ${EXCALIDRAW_SEMANTICS.error.stroke}`,
|
|
132
|
+
`evidence: ${EXCALIDRAW_SEMANTICS.evidence.fill} / ${EXCALIDRAW_SEMANTICS.evidence.stroke} inactive: ${EXCALIDRAW_SEMANTICS.inactive.fill} / ${EXCALIDRAW_SEMANTICS.inactive.stroke}`,
|
|
133
|
+
"```",
|
|
134
|
+
"",
|
|
135
|
+
"`/style palette` — render visual swatch · `/style d2` — D2 style template",
|
|
136
|
+
"`/style excalidraw` — semantic palette table · `/style check <file>` — audit colors",
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function excalidrawTable(): string {
|
|
141
|
+
const rows = Object.entries(EXCALIDRAW_SEMANTICS).map(
|
|
142
|
+
([name, { fill, stroke, use }]) => `| \`${name}\` | \`${fill}\` | \`${stroke}\` | ${use} |`
|
|
143
|
+
);
|
|
144
|
+
return [
|
|
145
|
+
"**Excalidraw Semantic Palette**",
|
|
146
|
+
"",
|
|
147
|
+
"| Purpose | Fill | Stroke | Use |",
|
|
148
|
+
"|---------|------|--------|-----|",
|
|
149
|
+
...rows,
|
|
150
|
+
"",
|
|
151
|
+
"Text on all fills: use `#c4d8e4` (Alpharius silver-white foreground)",
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function d2Template(): string {
|
|
156
|
+
return [
|
|
157
|
+
"**D2 Alpharius Style Template**",
|
|
158
|
+
"",
|
|
159
|
+
"Copy and adapt for your diagrams. Renders with `--theme 200 --layout elk`:",
|
|
160
|
+
"",
|
|
161
|
+
"```d2",
|
|
162
|
+
D2_STYLE_TEMPLATE,
|
|
163
|
+
"```",
|
|
164
|
+
].join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function auditColors(filePath: string): string {
|
|
168
|
+
const fs = require("node:fs");
|
|
169
|
+
let content: string;
|
|
170
|
+
try {
|
|
171
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
172
|
+
} catch {
|
|
173
|
+
return `❌ Could not read file: \`${filePath}\``;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const hexPattern = /#[0-9a-fA-F]{6}\b/g;
|
|
177
|
+
const found = new Set<string>();
|
|
178
|
+
let match: RegExpExecArray | null;
|
|
179
|
+
while ((match = hexPattern.exec(content)) !== null) {
|
|
180
|
+
found.add(match[0].toLowerCase());
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (found.size === 0) {
|
|
184
|
+
return `✅ No hex colors found in \`${filePath}\``;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const onPalette: string[] = [];
|
|
188
|
+
const offPalette: string[] = [];
|
|
189
|
+
const tokensByHex = new Map<string, string[]>();
|
|
190
|
+
for (const [name, hex] of Object.entries(ALL_TOKENS)) {
|
|
191
|
+
const h = hex.toLowerCase();
|
|
192
|
+
if (!tokensByHex.has(h)) tokensByHex.set(h, []);
|
|
193
|
+
tokensByHex.get(h)!.push(name);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const hex of [...found].sort()) {
|
|
197
|
+
const tokens = tokensByHex.get(hex);
|
|
198
|
+
if (tokens) {
|
|
199
|
+
onPalette.push(` ✅ \`${hex}\` → ${tokens.join(", ")}`);
|
|
200
|
+
} else {
|
|
201
|
+
offPalette.push(` ⚠️ \`${hex}\` — **off-palette**`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const lines = [`**Color Audit: \`${filePath}\`**`, "", `Found ${found.size} unique hex colors:`, ""];
|
|
206
|
+
if (onPalette.length) lines.push("**On-palette:**", ...onPalette, "");
|
|
207
|
+
if (offPalette.length) lines.push("**Off-palette:**", ...offPalette, "");
|
|
208
|
+
return lines.join("\n");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Extension
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
export default function styleExtension(pi: ExtensionAPI) {
|
|
216
|
+
pi.registerCommand("style", {
|
|
217
|
+
description: "Alpharius design system (usage: /style [palette|d2|excalidraw|check <file>])",
|
|
218
|
+
getArgumentCompletions: (prefix: string) => {
|
|
219
|
+
const parts = prefix.split(/\s+/);
|
|
220
|
+
if (parts.length <= 1) {
|
|
221
|
+
const subs: Array<{ value: string; label: string; description: string }> = [
|
|
222
|
+
{ value: "palette", label: "palette", description: "Render visual swatch" },
|
|
223
|
+
{ value: "d2", label: "d2", description: "D2 style template" },
|
|
224
|
+
{ value: "excalidraw", label: "excalidraw", description: "Semantic palette table" },
|
|
225
|
+
{ value: "check", label: "check", description: "Audit file colors" },
|
|
226
|
+
];
|
|
227
|
+
const filtered = subs.filter(s => s.value.startsWith(parts[0] || ""));
|
|
228
|
+
return filtered.length > 0 ? filtered : null;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
},
|
|
232
|
+
handler: async (args, _ctx) => {
|
|
233
|
+
const trimmed = (args || "").trim();
|
|
234
|
+
const [subcommand, ...rest] = trimmed.split(/\s+/);
|
|
235
|
+
|
|
236
|
+
let output: string;
|
|
237
|
+
|
|
238
|
+
switch (subcommand || "") {
|
|
239
|
+
case "":
|
|
240
|
+
output = quickRef();
|
|
241
|
+
break;
|
|
242
|
+
case "palette":
|
|
243
|
+
// Delegate to agent to render via render_diagram tool (D2)
|
|
244
|
+
pi.sendUserMessage(
|
|
245
|
+
"Render a D2 diagram showing the Alpharius palette as a visual swatch. " +
|
|
246
|
+
"Use the style skill's color tokens. Group into containers: Core (bg/cardBg/surfaceBg + primary/primaryMuted/primaryBright + fg/mutedFg/dimFg), " +
|
|
247
|
+
"Signals (green/red/orange/yellow), and Borders (borderColor/borderDim). " +
|
|
248
|
+
"Style each node with its actual hex value as fill color, appropriate font-color for contrast, " +
|
|
249
|
+
"and label it with token name + hex value. Use D2 style blocks.",
|
|
250
|
+
{ deliverAs: "followUp" },
|
|
251
|
+
);
|
|
252
|
+
return;
|
|
253
|
+
case "d2":
|
|
254
|
+
output = d2Template();
|
|
255
|
+
break;
|
|
256
|
+
case "excalidraw":
|
|
257
|
+
output = excalidrawTable();
|
|
258
|
+
break;
|
|
259
|
+
case "check": {
|
|
260
|
+
const filePath = rest.join(" ");
|
|
261
|
+
if (!filePath) {
|
|
262
|
+
output = "Usage: `/style check <file path>`";
|
|
263
|
+
} else {
|
|
264
|
+
output = auditColors(filePath);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
default:
|
|
269
|
+
// Treat as a question — delegate to agent with style skill context
|
|
270
|
+
pi.sendUserMessage(
|
|
271
|
+
`The user asked about the style system: "${trimmed}". ` +
|
|
272
|
+
`Answer using the style skill (Alpharius design system). Load /skill:style if needed.`,
|
|
273
|
+
{ deliverAs: "followUp" },
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
pi.sendMessage({ customType: "view", content: output, display: true });
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|