@ganglion/xacpx 0.9.2 → 0.10.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/dist/bridge/bridge-main.js +170 -80
- package/dist/channels/types.d.ts +11 -0
- package/dist/channels/weixin-channel.d.ts +8 -0
- package/dist/cli.js +1456 -833
- package/dist/commands/handlers/later-handler.d.ts +3 -3
- package/dist/commands/parse-command.d.ts +1 -0
- package/dist/commands/router-types.d.ts +1 -1
- package/dist/config/config-store.d.ts +44 -5
- package/dist/config/types.d.ts +8 -0
- package/dist/i18n/types.d.ts +7 -3
- package/dist/orchestration/coordinator-identity.d.ts +27 -0
- package/dist/orchestration/orchestration-service.d.ts +0 -23
- package/dist/perf/perf-log-writer.d.ts +1 -0
- package/dist/plugin-api.js +24 -8
- package/dist/scheduled/scheduled-service.d.ts +5 -2
- package/dist/sessions/session-service.d.ts +7 -3
- package/dist/state/state-store.d.ts +69 -2
- package/dist/util/private-file.d.ts +12 -0
- package/package.json +1 -1
|
@@ -3,10 +3,10 @@ import type { ScheduledSessionMode } from "../../scheduled/scheduled-types";
|
|
|
3
3
|
import type { HelpTopicMetadata } from "../help/help-types";
|
|
4
4
|
export declare function laterHelp(): HelpTopicMetadata;
|
|
5
5
|
export declare function handleLaterHelp(): RouterResponse;
|
|
6
|
-
export declare function handleLaterCreate(tokens: string[], scheduled: ScheduledRouterOps, chatKey: string, currentSession: {
|
|
6
|
+
export declare function handleLaterCreate(tokens: string[], tails: string[], scheduled: ScheduledRouterOps, chatKey: string, currentSession: {
|
|
7
7
|
alias: string;
|
|
8
8
|
agent: string;
|
|
9
9
|
workspace: string;
|
|
10
10
|
} | null, defaultMode: ScheduledSessionMode, accountId?: string, replyContextToken?: string): Promise<RouterResponse>;
|
|
11
|
-
export declare function handleLaterList(scheduled: ScheduledRouterOps): RouterResponse;
|
|
12
|
-
export declare function handleLaterCancel(id: string, scheduled: ScheduledRouterOps): Promise<RouterResponse>;
|
|
11
|
+
export declare function handleLaterList(scheduled: ScheduledRouterOps, chatKey: string): RouterResponse;
|
|
12
|
+
export declare function handleLaterCancel(id: string, scheduled: ScheduledRouterOps, chatKey: string): Promise<RouterResponse>;
|
|
@@ -41,7 +41,7 @@ export interface ActiveHumanQuestionPackageContext {
|
|
|
41
41
|
}>;
|
|
42
42
|
queuedCount: number;
|
|
43
43
|
}
|
|
44
|
-
export type WritableConfigStore = Pick<ConfigStore, "load" | "
|
|
44
|
+
export type WritableConfigStore = Pick<ConfigStore, "load" | "upsertWorkspace" | "removeWorkspace" | "upsertAgent" | "removeAgent" | "updateTransport" | "updateChannel" | "getRawValue" | "setRawValue" | "unsetRawValue">;
|
|
45
45
|
export interface CommandRouterContext {
|
|
46
46
|
sessions: SessionService;
|
|
47
47
|
transport: SessionTransport;
|
|
@@ -1,13 +1,52 @@
|
|
|
1
|
-
import type { AppConfig } from "./types";
|
|
1
|
+
import type { AgentConfig, AppConfig, ChannelRuntimeConfig, PluginConfig } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Raw-patch config persistence.
|
|
4
|
+
*
|
|
5
|
+
* The parsed `AppConfig` is a READ model only: parsing drops unknown keys
|
|
6
|
+
* (e.g. `workspaces.*.allowed_agents`), expands `~` in workspace cwds, and
|
|
7
|
+
* materializes every default. Serializing it back would destroy a hand-edited
|
|
8
|
+
* config.json. Every mutation therefore patches the raw JSON document read
|
|
9
|
+
* straight from disk, touching only the targeted subtree, and never writes a
|
|
10
|
+
* parsed config object.
|
|
11
|
+
*/
|
|
12
|
+
export type RawConfigPathSegment = string | {
|
|
13
|
+
/** Addresses the entry of a JSON array whose `id` property equals this value. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** When set, a missing entry is materialized from this template on writes. */
|
|
16
|
+
createWith?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
export type RawConfigPath = readonly RawConfigPathSegment[];
|
|
19
|
+
export type RawConfigLookup = {
|
|
20
|
+
present: true;
|
|
21
|
+
value: unknown;
|
|
22
|
+
} | {
|
|
23
|
+
present: false;
|
|
24
|
+
};
|
|
2
25
|
export declare class ConfigStore {
|
|
3
26
|
private readonly path;
|
|
4
27
|
constructor(path: string);
|
|
5
28
|
load(): Promise<AppConfig>;
|
|
6
|
-
|
|
29
|
+
/** Reads the raw (unparsed) value at `path`, e.g. to capture it for a rollback. */
|
|
30
|
+
getRawValue(path: RawConfigPath): Promise<RawConfigLookup>;
|
|
31
|
+
setRawValue(path: RawConfigPath, value: unknown): Promise<AppConfig>;
|
|
32
|
+
unsetRawValue(path: RawConfigPath): Promise<AppConfig>;
|
|
7
33
|
upsertWorkspace(name: string, cwd: string, description?: string): Promise<AppConfig>;
|
|
8
34
|
removeWorkspace(name: string): Promise<AppConfig>;
|
|
9
|
-
upsertAgent(name: string, agent:
|
|
35
|
+
upsertAgent(name: string, agent: AgentConfig): Promise<AppConfig>;
|
|
10
36
|
removeAgent(name: string): Promise<AppConfig>;
|
|
11
|
-
|
|
12
|
-
|
|
37
|
+
/** Sets only the given transport keys; a key explicitly set to `undefined` is removed. */
|
|
38
|
+
updateTransport(patch: Partial<AppConfig["transport"]>): Promise<AppConfig>;
|
|
39
|
+
/** Sets only the given channel keys; a key explicitly set to `undefined` is removed. */
|
|
40
|
+
updateChannel(patch: Partial<AppConfig["channel"]>): Promise<AppConfig>;
|
|
41
|
+
/** Replaces the tool-managed `plugins` array; everything else stays untouched. */
|
|
42
|
+
replacePlugins(plugins: PluginConfig[]): Promise<AppConfig>;
|
|
43
|
+
/** Replaces the tool-managed `channels` array; everything else stays untouched. */
|
|
44
|
+
replaceChannels(channels: ChannelRuntimeConfig[]): Promise<AppConfig>;
|
|
45
|
+
private patchRaw;
|
|
46
|
+
private readRaw;
|
|
13
47
|
}
|
|
48
|
+
export declare function serializeRawConfig(raw: Record<string, unknown>): string;
|
|
49
|
+
export declare function readRawConfigValue(root: Record<string, unknown>, path: RawConfigPath): RawConfigLookup;
|
|
50
|
+
export declare function setRawConfigValue(root: Record<string, unknown>, path: RawConfigPath, value: unknown): void;
|
|
51
|
+
export declare function unsetRawConfigValue(root: Record<string, unknown>, path: RawConfigPath): void;
|
|
52
|
+
export declare function assertSafeConfigKey(key: string): void;
|
package/dist/config/types.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export type WechatReplyMode = ReplyMode;
|
|
|
7
7
|
export interface ChannelConfig {
|
|
8
8
|
type: string;
|
|
9
9
|
replyMode: ReplyMode;
|
|
10
|
+
/**
|
|
11
|
+
* Sender ids the operator trusts as channel owners. Group turns from these
|
|
12
|
+
* senders pass owner-gated command authorization even when the channel
|
|
13
|
+
* protocol carries no group-role information (e.g. WeChat).
|
|
14
|
+
*/
|
|
15
|
+
ownerIds?: string[];
|
|
10
16
|
options?: Record<string, unknown>;
|
|
11
17
|
}
|
|
12
18
|
/** @deprecated Legacy input shape only. Use ChannelConfig. */
|
|
@@ -67,6 +73,8 @@ export interface ChannelRuntimeConfig {
|
|
|
67
73
|
type: string;
|
|
68
74
|
enabled: boolean;
|
|
69
75
|
replyMode?: ReplyMode;
|
|
76
|
+
/** See ChannelConfig.ownerIds — per-channel trusted owner sender ids. */
|
|
77
|
+
ownerIds?: string[];
|
|
70
78
|
options?: Record<string, unknown>;
|
|
71
79
|
}
|
|
72
80
|
export interface PluginConfig {
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface SessionMessages {
|
|
|
11
11
|
currentLabel: string;
|
|
12
12
|
sessionListItem: (alias: string, agent: string, workspace: string) => string;
|
|
13
13
|
sessionCreated: (alias: string) => string;
|
|
14
|
+
sessionAlreadyExists: (alias: string, agent: string, workspace: string) => string;
|
|
14
15
|
sessionAttachNotFound: (alias: string, agent: string, workspace: string) => string;
|
|
15
16
|
sessionAttached: (alias: string) => string;
|
|
16
17
|
switched: (alias: string, agent: string, workspace: string) => string;
|
|
@@ -43,6 +44,7 @@ export interface SessionMessages {
|
|
|
43
44
|
sessionBlockedByTasksHint: string;
|
|
44
45
|
sessionRemoved: (alias: string) => string;
|
|
45
46
|
sessionRemovedWasActive: string;
|
|
47
|
+
sessionRemovedWasActivePromoted: (alias: string) => string;
|
|
46
48
|
sessionTransportShared: (transportSession: string, count: number) => string;
|
|
47
49
|
sessionOrchestrationPurgeFailed: (warning: string) => string;
|
|
48
50
|
sessionTransportTeardownFailed: (warning: string) => string;
|
|
@@ -635,7 +637,6 @@ export interface CliUpdateMessages {
|
|
|
635
637
|
updateFailed: (name: string, error: string) => string;
|
|
636
638
|
targetNotFound: (name: string) => string;
|
|
637
639
|
targetVersionUnknown: (name: string) => string;
|
|
638
|
-
targetNotPinned: (name: string) => string;
|
|
639
640
|
multiTargetNonInteractive: string;
|
|
640
641
|
selectionPrompt: string;
|
|
641
642
|
selectionInvalid: (part: string) => string;
|
|
@@ -690,6 +691,8 @@ export interface PluginCliMessages {
|
|
|
690
691
|
noPlugins: string;
|
|
691
692
|
pluginListHeader: string;
|
|
692
693
|
unrecognizedArgs: (args: string) => string;
|
|
694
|
+
pluginSpecHasDoubleQuote: (spec: string) => string;
|
|
695
|
+
pluginSpecHasPercentOnWindows: (spec: string) => string;
|
|
693
696
|
pluginInstallFailed: (packageSpec: string, error: string) => string;
|
|
694
697
|
pluginValidateFailed: (recordedName: string, error: string) => string;
|
|
695
698
|
pluginInstalled: (recordedName: string) => string;
|
|
@@ -776,8 +779,6 @@ export interface WeixinMessages {
|
|
|
776
779
|
debugEnabled: string;
|
|
777
780
|
debugDisabled: string;
|
|
778
781
|
sessionCleared: string;
|
|
779
|
-
noAccountsLoggedIn: string;
|
|
780
|
-
logoutSuccess: string;
|
|
781
782
|
commandFailed: (detail: string) => string;
|
|
782
783
|
}
|
|
783
784
|
export interface MigrateMessages {
|
|
@@ -803,6 +804,7 @@ export interface MiscMessages {
|
|
|
803
804
|
quotaHeadsUp: string;
|
|
804
805
|
quotaOverflowSummary: (count: number) => string;
|
|
805
806
|
finalHeadsUp: (total: number, sentSoFar: number, remaining: number) => string;
|
|
807
|
+
finalAllParked: (count: number) => string;
|
|
806
808
|
quotedMessagePrefix: (parts: string) => string;
|
|
807
809
|
scheduledTaskFailed: (message: string) => string;
|
|
808
810
|
orchestrationTaskCompleted: (taskId: string, workerSession: string, result: string) => string;
|
|
@@ -825,6 +827,8 @@ export interface MiscMessages {
|
|
|
825
827
|
delegateQPackageInstr3: string;
|
|
826
828
|
commandAccessDeniedSuffix: string;
|
|
827
829
|
commandAccessDeniedHint: string;
|
|
830
|
+
commandAccessDeniedChatTypeMissingSuffix: string;
|
|
831
|
+
commandAccessDeniedChatTypeMissingHint: string;
|
|
828
832
|
commandLabelThisMessage: string;
|
|
829
833
|
sessionResetNoCurrentSession: string;
|
|
830
834
|
sessionResetFailed: (alias: string) => string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The orchestration coordinator identity is derived from a session's transport
|
|
3
|
+
* name. `/clear` rotates that name from `workspace:alias` to
|
|
4
|
+
* `workspace:alias:reset-<timestamp>` (see session-reset-handler), which would
|
|
5
|
+
* otherwise orphan every task delegated before the reset. Stripping the
|
|
6
|
+
* volatile `:reset-<digits>` suffix yields the stable `workspace:alias` identity
|
|
7
|
+
* so ownership survives `/clear`.
|
|
8
|
+
*
|
|
9
|
+
* Pure leaf module: do not add imports, so it can be used from sessions/,
|
|
10
|
+
* commands/, and orchestration/ without risking an import cycle.
|
|
11
|
+
*
|
|
12
|
+
* No-op on any value lacking a trailing `:reset-<digits>` segment, so external
|
|
13
|
+
* coordinators (`external_*`) and normal sessions pass through unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export declare function stableCoordinatorSession(transportSession: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The single chokepoint for asking "do these two transport names refer to the
|
|
18
|
+
* same coordinator?". Both sides are reduced to their stable identity before
|
|
19
|
+
* comparison, so it is robust to either side carrying a volatile
|
|
20
|
+
* `:reset-<digits>` suffix (a live post-`/clear` session, or a legacy
|
|
21
|
+
* state.json record persisted before the identity was normalized at write).
|
|
22
|
+
*
|
|
23
|
+
* Every coordinator-ownership comparison must go through this rather than a raw
|
|
24
|
+
* `===`, so the normalization rule lives in one place instead of being
|
|
25
|
+
* re-derived (and inconsistently forgotten) at each call site.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sameCoordinatorSession(a: string, b: string): boolean;
|
|
@@ -206,23 +206,6 @@ export interface CleanTasksResult {
|
|
|
206
206
|
removedTasks: number;
|
|
207
207
|
removedBindings: number;
|
|
208
208
|
}
|
|
209
|
-
export type ResetGcTrigger = "startup" | "interval";
|
|
210
|
-
export interface PurgeExpiredResetCoordinatorsInput {
|
|
211
|
-
cutoffDays: number;
|
|
212
|
-
trigger: ResetGcTrigger;
|
|
213
|
-
}
|
|
214
|
-
export interface PurgeExpiredResetCoordinatorsResult {
|
|
215
|
-
candidates: number;
|
|
216
|
-
purgedCoordinators: number;
|
|
217
|
-
removed: {
|
|
218
|
-
tasks: number;
|
|
219
|
-
workerBindings: number;
|
|
220
|
-
groups: number;
|
|
221
|
-
coordinatorRoutes: number;
|
|
222
|
-
humanQuestionPackages: number;
|
|
223
|
-
coordinatorQuestionState: number;
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
209
|
export interface OrchestrationTaskFilter {
|
|
227
210
|
sourceHandle?: string;
|
|
228
211
|
coordinatorSession?: string;
|
|
@@ -361,7 +344,6 @@ export declare class OrchestrationService {
|
|
|
361
344
|
cleanTasks(coordinatorSession: string): Promise<CleanTasksResult>;
|
|
362
345
|
listSessionBlockingTasks(transportSession: string): Promise<OrchestrationTaskRecord[]>;
|
|
363
346
|
purgeSessionReferences(transportSession: string): Promise<CleanTasksResult>;
|
|
364
|
-
purgeExpiredResetCoordinators(input: PurgeExpiredResetCoordinatorsInput): Promise<PurgeExpiredResetCoordinatorsResult>;
|
|
365
347
|
listPendingCoordinatorResults(coordinatorSession: string): Promise<OrchestrationTaskRecord[]>;
|
|
366
348
|
listPendingCoordinatorBlockers(coordinatorSession: string): Promise<OrchestrationTaskRecord[]>;
|
|
367
349
|
listContestedCoordinatorResults(coordinatorSession: string): Promise<OrchestrationTaskRecord[]>;
|
|
@@ -439,11 +421,6 @@ export declare class OrchestrationService {
|
|
|
439
421
|
private ensureGroups;
|
|
440
422
|
private removeEmptyGroupsForCoordinator;
|
|
441
423
|
private removeCoordinatorMetadataIfUnused;
|
|
442
|
-
private isResetCoordinatorSession;
|
|
443
|
-
private collectResetCoordinatorCandidates;
|
|
444
|
-
private parseDateMs;
|
|
445
|
-
private resolveResetCoordinatorActivityAtMs;
|
|
446
|
-
private cascadeRemoveCoordinatorRecords;
|
|
447
424
|
private bumpGroupUpdated;
|
|
448
425
|
private getLatestDeliveredPackageMessage;
|
|
449
426
|
private snapshotCoordinatorDeliveryRoute;
|
package/dist/plugin-api.js
CHANGED
|
@@ -68,6 +68,11 @@ var init_session = __esm(() => {
|
|
|
68
68
|
currentLabel: "[current]",
|
|
69
69
|
sessionListItem: (alias, agent, workspace) => `- ${alias} (${agent} @ ${workspace})`,
|
|
70
70
|
sessionCreated: (alias) => `Session "${alias}" created and switched.`,
|
|
71
|
+
sessionAlreadyExists: (alias, agent, workspace) => [
|
|
72
|
+
`Session "${alias}" already exists (${agent} @ ${workspace}).`,
|
|
73
|
+
`Switch to it with /use ${alias}, or remove it first with /session rm ${alias}.`
|
|
74
|
+
].join(`
|
|
75
|
+
`),
|
|
71
76
|
sessionAttachNotFound: (alias, agent, workspace) => [
|
|
72
77
|
"No existing session found to attach.",
|
|
73
78
|
`Check the session name and retry: /session attach ${alias} --agent ${agent} --ws ${workspace} --name <session-name>`
|
|
@@ -104,6 +109,7 @@ var init_session = __esm(() => {
|
|
|
104
109
|
sessionBlockedByTasksHint: "Use /tasks to list tasks, or /task cancel <id> to cancel one.",
|
|
105
110
|
sessionRemoved: (alias) => `Session "${alias}" removed.`,
|
|
106
111
|
sessionRemovedWasActive: "This was the active session. Its chat context has been cleared.",
|
|
112
|
+
sessionRemovedWasActivePromoted: (alias) => `This was the active session. Switched back to the previous session "${alias}".`,
|
|
107
113
|
sessionTransportShared: (transportSession, count) => `Note: backend session "${transportSession}" is still referenced by ${count} other session(s) and was not closed.`,
|
|
108
114
|
sessionOrchestrationPurgeFailed: (warning) => `Note: failed to purge orchestration references (${warning}). Run /tasks clean manually to clean up.`,
|
|
109
115
|
sessionTransportTeardownFailed: (warning) => `Note: backend session could not be closed automatically (${warning}). Run acpx sessions close manually if needed.`,
|
|
@@ -397,7 +403,7 @@ var init_later = __esm(() => {
|
|
|
397
403
|
helpNote2: "Time must be at least 10 seconds and at most 7 days away",
|
|
398
404
|
helpNote3: "By default runs in a new temporary session that is destroyed after completion",
|
|
399
405
|
helpNote4: "Use --bind to send to the session that was current when the task was created (configurable via later.defaultMode)",
|
|
400
|
-
helpNote5: "/lt list shows
|
|
406
|
+
helpNote5: "/lt list shows only this chat's pending tasks; in group chats only the owner can cancel",
|
|
401
407
|
helpNote6: "Scheduling slash-prefixed xacpx commands is not supported",
|
|
402
408
|
helpNote7: "Full time format reference: docs/later-command.md"
|
|
403
409
|
};
|
|
@@ -826,7 +832,6 @@ var init_cli_update = __esm(() => {
|
|
|
826
832
|
updateFailed: (name, error) => `${name} update failed: ${error}`,
|
|
827
833
|
targetNotFound: (name) => `Update target not found: ${name}`,
|
|
828
834
|
targetVersionUnknown: (name) => `${name}: cannot check latest version; skipped.`,
|
|
829
|
-
targetNotPinned: (name) => `${name} has no recorded version; use \`xacpx plugin update ${name}\` or specify a version explicitly.`,
|
|
830
835
|
multiTargetNonInteractive: "Installed plugins detected; in non-interactive mode use `xacpx update --all` or `xacpx update <name>`.",
|
|
831
836
|
selectionPrompt: "Select items to update (numbers, comma-separated; a=all; Enter to cancel): ",
|
|
832
837
|
selectionInvalid: (part) => `Invalid selection: ${part}`,
|
|
@@ -891,6 +896,8 @@ var init_plugin_cli = __esm(() => {
|
|
|
891
896
|
noPlugins: "No plugins installed yet.",
|
|
892
897
|
pluginListHeader: "Plugins:",
|
|
893
898
|
unrecognizedArgs: (args) => `Unrecognized arguments: ${args}`,
|
|
899
|
+
pluginSpecHasDoubleQuote: (spec) => `Invalid plugin spec ${spec}: double quotes (") are never valid in an npm package spec.`,
|
|
900
|
+
pluginSpecHasPercentOnWindows: (spec) => `Invalid plugin spec ${spec}: "%" would be mangled by cmd.exe on Windows. Install the package with npm directly instead.`,
|
|
894
901
|
pluginInstallFailed: (packageSpec, error) => `Plugin ${packageSpec} install failed: ${error}`,
|
|
895
902
|
pluginValidateFailed: (recordedName, error) => `Plugin ${recordedName} validation failed: ${error}`,
|
|
896
903
|
pluginInstalled: (recordedName) => `Plugin ${recordedName} installed`,
|
|
@@ -1002,8 +1009,6 @@ var init_weixin = __esm(() => {
|
|
|
1002
1009
|
debugEnabled: "Debug mode enabled",
|
|
1003
1010
|
debugDisabled: "Debug mode disabled",
|
|
1004
1011
|
sessionCleared: "✅ Session cleared. Starting a fresh conversation.",
|
|
1005
|
-
noAccountsLoggedIn: "No accounts are currently logged in.",
|
|
1006
|
-
logoutSuccess: "✅ Logged out. All account credentials cleared.",
|
|
1007
1012
|
commandFailed: (detail) => `❌ Command failed: ${detail}`
|
|
1008
1013
|
};
|
|
1009
1014
|
});
|
|
@@ -1041,6 +1046,7 @@ var init_misc = __esm(() => {
|
|
|
1041
1046
|
quotaOverflowSummary: (count) => `(${count} progress updates omitted due to message limit; see final result below)`,
|
|
1042
1047
|
finalHeadsUp: (total, sentSoFar, remaining) => `—
|
|
1043
1048
|
\uD83D\uDCC4 Result: ${total} parts total, ${sentSoFar} sent. Reply /jx to see the next ${remaining} parts.`,
|
|
1049
|
+
finalAllParked: (count) => `\uD83D\uDCC4 Message limit reached: the result (${count} parts) is parked. Reply /jx to receive it.`,
|
|
1044
1050
|
quotedMessagePrefix: (parts) => `[Quote: ${parts}]`,
|
|
1045
1051
|
scheduledTaskFailed: (message) => `Scheduled task failed: ${message}`,
|
|
1046
1052
|
orchestrationTaskCompleted: (taskId, workerSession, result) => `Delegation task "${taskId}" completed
|
|
@@ -1069,6 +1075,8 @@ var init_misc = __esm(() => {
|
|
|
1069
1075
|
delegateQPackageInstr3: "Do not forward the human's exact words to the worker",
|
|
1070
1076
|
commandAccessDeniedSuffix: " is restricted to group owner only.",
|
|
1071
1077
|
commandAccessDeniedHint: "To perform control operations, have the owner send them in the group, or use a private chat.",
|
|
1078
|
+
commandAccessDeniedChatTypeMissingSuffix: " was blocked: this channel did not report the chat type (direct or group), so control commands are disabled here.",
|
|
1079
|
+
commandAccessDeniedChatTypeMissingHint: "Read-only commands and prompts still work. This is a channel metadata issue — update or report the channel plugin.",
|
|
1072
1080
|
commandLabelThisMessage: "This message",
|
|
1073
1081
|
sessionResetNoCurrentSession: "No session is currently selected. Run /session new ... or /use <alias> first.",
|
|
1074
1082
|
sessionResetFailed: (alias) => `Session "${alias}" reset failed. The new backend session was not created, please try again later.`,
|
|
@@ -1139,6 +1147,11 @@ var init_session2 = __esm(() => {
|
|
|
1139
1147
|
currentLabel: "[当前]",
|
|
1140
1148
|
sessionListItem: (alias, agent2, workspace2) => `- ${alias} (${agent2} @ ${workspace2})`,
|
|
1141
1149
|
sessionCreated: (alias) => `会话「${alias}」已创建并切换`,
|
|
1150
|
+
sessionAlreadyExists: (alias, agent2, workspace2) => [
|
|
1151
|
+
`会话「${alias}」已存在(${agent2} @ ${workspace2})。`,
|
|
1152
|
+
`发送 /use ${alias} 切换到它,或先执行 /session rm ${alias} 删除后再创建。`
|
|
1153
|
+
].join(`
|
|
1154
|
+
`),
|
|
1142
1155
|
sessionAttachNotFound: (alias, agent2, workspace2) => [
|
|
1143
1156
|
"没有找到可绑定的已有会话。",
|
|
1144
1157
|
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent2} --ws ${workspace2} --name <会话名>`
|
|
@@ -1175,6 +1188,7 @@ var init_session2 = __esm(() => {
|
|
|
1175
1188
|
sessionBlockedByTasksHint: "使用 /tasks 查看任务列表,或 /task cancel <id> 取消任务。",
|
|
1176
1189
|
sessionRemoved: (alias) => `已删除会话「${alias}」。`,
|
|
1177
1190
|
sessionRemovedWasActive: "该会话是当前活跃会话,已自动清除相关聊天上下文。",
|
|
1191
|
+
sessionRemovedWasActivePromoted: (alias) => `该会话是当前活跃会话,已切换回上一个会话「${alias}」。`,
|
|
1178
1192
|
sessionTransportShared: (transportSession, count) => `提示:后端会话「${transportSession}」仍被其他 ${count} 个会话引用,未关闭。`,
|
|
1179
1193
|
sessionOrchestrationPurgeFailed: (warning) => `提示:清理任务编排引用失败(${warning}),请稍后执行 /tasks clean 手动清理。`,
|
|
1180
1194
|
sessionTransportTeardownFailed: (warning) => `提示:后端会话未能自动关闭(${warning}),如有残留请手动执行 acpx sessions close。`,
|
|
@@ -1468,7 +1482,7 @@ var init_later2 = __esm(() => {
|
|
|
1468
1482
|
helpNote2: "时间必须在 10 秒之后、7 天之内",
|
|
1469
1483
|
helpNote3: "默认在为本次任务新建的临时会话里执行,跑完即销毁",
|
|
1470
1484
|
helpNote4: "加 --bind 改为发送到创建时绑定的当前会话(默认模式可用 later.defaultMode 配置)",
|
|
1471
|
-
helpNote5: "/lt list
|
|
1485
|
+
helpNote5: "/lt list 只显示本聊天创建的待执行任务;群聊中只有群主可取消",
|
|
1472
1486
|
helpNote6: "不支持延迟执行 / 开头的 xacpx 命令",
|
|
1473
1487
|
helpNote7: "完整时间格式与说明见 docs/later-command.md"
|
|
1474
1488
|
};
|
|
@@ -1897,7 +1911,6 @@ var init_cli_update2 = __esm(() => {
|
|
|
1897
1911
|
updateFailed: (name, error) => `${name} 更新失败:${error}`,
|
|
1898
1912
|
targetNotFound: (name) => `没有找到更新项:${name}`,
|
|
1899
1913
|
targetVersionUnknown: (name) => `${name} 无法检查最新版本,已跳过。`,
|
|
1900
|
-
targetNotPinned: (name) => `${name} 未记录当前版本;请先使用 \`xacpx plugin update ${name}\` 或显式选择版本。`,
|
|
1901
1914
|
multiTargetNonInteractive: "检测到已安装插件;非交互模式请使用 `xacpx update --all` 或 `xacpx update <name>`。",
|
|
1902
1915
|
selectionPrompt: "请选择要更新的项目(数字,逗号分隔,a=全部,回车取消):",
|
|
1903
1916
|
selectionInvalid: (part) => `无效选择:${part}`,
|
|
@@ -1962,6 +1975,8 @@ var init_plugin_cli2 = __esm(() => {
|
|
|
1962
1975
|
noPlugins: "还没有安装插件。",
|
|
1963
1976
|
pluginListHeader: "插件:",
|
|
1964
1977
|
unrecognizedArgs: (args) => `未识别的参数:${args}`,
|
|
1978
|
+
pluginSpecHasDoubleQuote: (spec) => `非法插件 spec ${spec}:npm 包 spec 不允许包含双引号 (")。`,
|
|
1979
|
+
pluginSpecHasPercentOnWindows: (spec) => `非法插件 spec ${spec}:Windows 上 cmd.exe 会展开 %,无法安全传递。请改用 npm 直接安装该包。`,
|
|
1965
1980
|
pluginInstallFailed: (packageSpec, error) => `插件 ${packageSpec} 安装失败:${error}`,
|
|
1966
1981
|
pluginValidateFailed: (recordedName, error) => `插件 ${recordedName} 校验失败:${error}`,
|
|
1967
1982
|
pluginInstalled: (recordedName) => `插件 ${recordedName} 已安装`,
|
|
@@ -2073,8 +2088,6 @@ var init_weixin2 = __esm(() => {
|
|
|
2073
2088
|
debugEnabled: "Debug 模式已开启",
|
|
2074
2089
|
debugDisabled: "Debug 模式已关闭",
|
|
2075
2090
|
sessionCleared: "✅ 会话已清除,重新开始对话",
|
|
2076
|
-
noAccountsLoggedIn: "当前没有已登录的账号",
|
|
2077
|
-
logoutSuccess: "✅ 已退出登录,清除所有账号凭证",
|
|
2078
2091
|
commandFailed: (detail) => `❌ 指令执行失败: ${detail}`
|
|
2079
2092
|
};
|
|
2080
2093
|
});
|
|
@@ -2112,6 +2125,7 @@ var init_misc2 = __esm(() => {
|
|
|
2112
2125
|
quotaOverflowSummary: (count) => `(因消息次数限制省略 ${count} 条进度,请继续查看下方最终结果)`,
|
|
2113
2126
|
finalHeadsUp: (total, sentSoFar, remaining) => `—
|
|
2114
2127
|
\uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`,
|
|
2128
|
+
finalAllParked: (count) => `\uD83D\uDCC4 已达消息上限:结果共 ${count} 段已暂存。回复 /jx 接收。`,
|
|
2115
2129
|
quotedMessagePrefix: (parts) => `[引用: ${parts}]`,
|
|
2116
2130
|
scheduledTaskFailed: (message) => `定时任务执行失败:${message}`,
|
|
2117
2131
|
orchestrationTaskCompleted: (taskId, workerSession, result) => `委派任务「${taskId}」已完成
|
|
@@ -2140,6 +2154,8 @@ var init_misc2 = __esm(() => {
|
|
|
2140
2154
|
delegateQPackageInstr3: "不要直接把 human 原话转发给 worker",
|
|
2141
2155
|
commandAccessDeniedSuffix: " 仅限群创建者/频道 owner 使用。",
|
|
2142
2156
|
commandAccessDeniedHint: "如果需要执行控制类操作,请由 owner 在群内发送,或改用私聊。",
|
|
2157
|
+
commandAccessDeniedChatTypeMissingSuffix: " 已被拦截:该频道未上报会话类型(直聊/群聊),控制类命令在此暂不可用。",
|
|
2158
|
+
commandAccessDeniedChatTypeMissingHint: "只读命令与普通对话不受影响。这是频道元数据问题,请升级或反馈该频道插件。",
|
|
2143
2159
|
commandLabelThisMessage: "该消息",
|
|
2144
2160
|
sessionResetNoCurrentSession: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。",
|
|
2145
2161
|
sessionResetFailed: (alias) => `会话「${alias}」重置失败。
|
|
@@ -28,8 +28,11 @@ export declare class ScheduledTaskService {
|
|
|
28
28
|
private readonly claimedInThisSession;
|
|
29
29
|
constructor(state: AppState, stateStore: Pick<StateStore, "save">, options?: ScheduledTaskServiceOptions);
|
|
30
30
|
createTask(input: CreateScheduledTaskInput): Promise<ScheduledTaskRecord>;
|
|
31
|
-
listPending(): ScheduledTaskRecord[];
|
|
32
|
-
|
|
31
|
+
listPending(chatKey: string): ScheduledTaskRecord[];
|
|
32
|
+
listPendingAllChats(): ScheduledTaskRecord[];
|
|
33
|
+
cancelPending(inputId: string, chatKey: string): Promise<boolean>;
|
|
34
|
+
cancelPendingAnyChat(inputId: string): Promise<boolean>;
|
|
35
|
+
private cancelPendingWhere;
|
|
33
36
|
markStartupMissed(): Promise<void>;
|
|
34
37
|
claimDueTasks(): Promise<ScheduledTaskRecord[]>;
|
|
35
38
|
markExecuted(id: string): Promise<void>;
|
|
@@ -67,9 +67,13 @@ export declare class SessionService {
|
|
|
67
67
|
createSession(alias: string, agent: string, workspace: string): Promise<ResolvedSession>;
|
|
68
68
|
/**
|
|
69
69
|
* All currently-known logical sessions resolved to transport sessions, deduped by
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* the composite identity acpx keys its session records on (agent + agent command +
|
|
71
|
+
* cwd + transport session name). Two aliases sharing a transport-session *name*
|
|
72
|
+
* but differing in agent/cwd (possible via /session attach) resolve to different
|
|
73
|
+
* acpx records with their own warm queue owners, so both must survive. Sessions
|
|
74
|
+
* whose agent or workspace is no longer registered are skipped (toResolvedSession
|
|
75
|
+
* would throw). Used by startup/shutdown cleanup to reap warm acpx queue owners;
|
|
76
|
+
* never throws.
|
|
73
77
|
*/
|
|
74
78
|
listAllResolvedSessions(): ResolvedSession[];
|
|
75
79
|
resolveSession(alias: string, agent: string, workspace: string, transportSession: string): ResolvedSession;
|
|
@@ -1,8 +1,75 @@
|
|
|
1
1
|
import { type AppState } from "./types";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Format version written on every save. The parser tolerates files without it
|
|
4
|
+
* (older releases) and ignores it on load; no migration chain exists yet.
|
|
5
|
+
*/
|
|
6
|
+
export declare const STATE_FILE_VERSION = 1;
|
|
7
|
+
/** A record or section that was skipped/repaired while loading state.json. */
|
|
8
|
+
export interface StateLoadDroppedRecord {
|
|
9
|
+
section: string;
|
|
10
|
+
key: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StateLoadReport {
|
|
14
|
+
dropped: StateLoadDroppedRecord[];
|
|
15
|
+
/** Backup copy of the original file, written because records were dropped. */
|
|
16
|
+
quarantinePath?: string;
|
|
17
|
+
/** Unreadable original renamed aside (whole-file JSON corruption). */
|
|
18
|
+
corruptPath?: string;
|
|
19
|
+
/** Best-effort backup/rename failure; load still returned the cleaned state. */
|
|
20
|
+
backupError?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Lenient state parser: a malformed record (or wrong-typed section) is skipped
|
|
24
|
+
* and collected in `dropped` instead of throwing, so one bad record can never
|
|
25
|
+
* brick daemon startup. The per-record shape checks themselves stay strict — an
|
|
26
|
+
* invalid record is quarantined, it never flows into dispatch logic. Only a
|
|
27
|
+
* non-object top level still throws (StateStore.load treats that as a corrupt
|
|
28
|
+
* file and renames it aside).
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseState(raw: unknown, path: string, dropped?: StateLoadDroppedRecord[]): AppState;
|
|
31
|
+
export interface StateStoreOptions {
|
|
32
|
+
/** Injectable clock used for quarantine/corrupt backup file names. */
|
|
33
|
+
now?: () => Date;
|
|
34
|
+
/**
|
|
35
|
+
* Injectable backup writer (tests simulate backup failures). May return the
|
|
36
|
+
* path actually written when it differs from the requested one (the default
|
|
37
|
+
* writer suffix-retries instead of overwriting an existing backup).
|
|
38
|
+
*/
|
|
39
|
+
writeBackup?: (targetPath: string, content: string) => Promise<string | void>;
|
|
40
|
+
}
|
|
41
|
+
/** Result of a side-effect-free {@link StateStore.inspect}. */
|
|
42
|
+
export interface StateLoadInspection {
|
|
43
|
+
state: AppState;
|
|
44
|
+
/** Null when the file is fully valid (or missing/empty). */
|
|
45
|
+
report: StateLoadReport | null;
|
|
46
|
+
}
|
|
3
47
|
export declare class StateStore {
|
|
4
48
|
private readonly path;
|
|
5
|
-
|
|
49
|
+
private readonly options;
|
|
50
|
+
private loadReport;
|
|
51
|
+
constructor(path: string, options?: StateStoreOptions);
|
|
52
|
+
/**
|
|
53
|
+
* Report of the most recent load(): null when the file was fully valid (or
|
|
54
|
+
* missing/empty), otherwise the dropped/repaired records plus the quarantine
|
|
55
|
+
* or corrupt backup path. Callers log/print this so silent repair is visible.
|
|
56
|
+
*/
|
|
57
|
+
get lastLoadReport(): StateLoadReport | null;
|
|
6
58
|
load(): Promise<AppState>;
|
|
59
|
+
/**
|
|
60
|
+
* Side-effect-free variant of load() for diagnostic callers (doctor): parses
|
|
61
|
+
* and reports exactly what load() would drop/repair, but never writes a
|
|
62
|
+
* quarantine backup, never renames a corrupt file, and does not touch
|
|
63
|
+
* {@link lastLoadReport}.
|
|
64
|
+
*/
|
|
65
|
+
inspect(): Promise<StateLoadInspection>;
|
|
66
|
+
private readAndParse;
|
|
7
67
|
save(state: AppState): Promise<void>;
|
|
68
|
+
private fileTimestamp;
|
|
69
|
+
/**
|
|
70
|
+
* Whole-file corruption (JSON syntax error / non-object top level): rename
|
|
71
|
+
* the file aside — not copy — so the next save does not fight the corrupt
|
|
72
|
+
* bytes, then start from an empty state.
|
|
73
|
+
*/
|
|
74
|
+
private recoverFromCorruptFile;
|
|
8
75
|
}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `fn` while holding the file's proper-lockfile lock, so a caller can make
|
|
3
|
+
* a whole read→modify→write span atomic with respect to other xacpx-aware
|
|
4
|
+
* processes (writePrivateFileAtomic alone only serializes the write).
|
|
5
|
+
*
|
|
6
|
+
* proper-lockfile is NOT reentrant: inside `fn`, write through the provided
|
|
7
|
+
* `writeLocked` (same atomic semantics, no re-lock) — never call
|
|
8
|
+
* writePrivateFileAtomic on the same path or it will deadlock until the lock
|
|
9
|
+
* goes stale. `realpath: false` keeps locking working for a not-yet-existing
|
|
10
|
+
* target file (the lock lives at `<path>.lock`).
|
|
11
|
+
*/
|
|
12
|
+
export declare function withPrivateFileLock<T>(path: string, fn: (writeLocked: (content: string) => Promise<void>) => Promise<T>): Promise<T>;
|
|
1
13
|
export declare function writePrivateFileAtomic(path: string, content: string): Promise<void>;
|
|
2
14
|
/**
|
|
3
15
|
* Synchronous private-file write for hot-path callers that cannot await
|