@shin1ohno/sage 0.8.8 → 0.9.1

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.
Files changed (78) hide show
  1. package/README.md +31 -0
  2. package/dist/cli/http-server-with-config.d.ts +2 -0
  3. package/dist/cli/http-server-with-config.d.ts.map +1 -1
  4. package/dist/cli/http-server-with-config.js +70 -0
  5. package/dist/cli/http-server-with-config.js.map +1 -1
  6. package/dist/cli/main-entry.d.ts.map +1 -1
  7. package/dist/cli/main-entry.js +1 -0
  8. package/dist/cli/main-entry.js.map +1 -1
  9. package/dist/cli/parser.d.ts +2 -0
  10. package/dist/cli/parser.d.ts.map +1 -1
  11. package/dist/cli/parser.js +7 -0
  12. package/dist/cli/parser.js.map +1 -1
  13. package/dist/config/validation.d.ts +721 -0
  14. package/dist/config/validation.d.ts.map +1 -1
  15. package/dist/config/validation.js +203 -0
  16. package/dist/config/validation.js.map +1 -1
  17. package/dist/integrations/calendar-service.d.ts +14 -0
  18. package/dist/integrations/calendar-service.d.ts.map +1 -1
  19. package/dist/integrations/calendar-service.js.map +1 -1
  20. package/dist/integrations/calendar-source-manager.d.ts +86 -3
  21. package/dist/integrations/calendar-source-manager.d.ts.map +1 -1
  22. package/dist/integrations/calendar-source-manager.js +146 -15
  23. package/dist/integrations/calendar-source-manager.js.map +1 -1
  24. package/dist/integrations/google-calendar-service.d.ts +55 -3
  25. package/dist/integrations/google-calendar-service.d.ts.map +1 -1
  26. package/dist/integrations/google-calendar-service.js +213 -4
  27. package/dist/integrations/google-calendar-service.js.map +1 -1
  28. package/dist/oauth/encryption-service.d.ts +104 -0
  29. package/dist/oauth/encryption-service.d.ts.map +1 -0
  30. package/dist/oauth/encryption-service.js +271 -0
  31. package/dist/oauth/encryption-service.js.map +1 -0
  32. package/dist/oauth/file-mutex.d.ts +68 -0
  33. package/dist/oauth/file-mutex.d.ts.map +1 -0
  34. package/dist/oauth/file-mutex.js +140 -0
  35. package/dist/oauth/file-mutex.js.map +1 -0
  36. package/dist/oauth/google-oauth-handler.d.ts +8 -9
  37. package/dist/oauth/google-oauth-handler.d.ts.map +1 -1
  38. package/dist/oauth/google-oauth-handler.js +30 -65
  39. package/dist/oauth/google-oauth-handler.js.map +1 -1
  40. package/dist/oauth/index.d.ts +1 -0
  41. package/dist/oauth/index.d.ts.map +1 -1
  42. package/dist/oauth/index.js +2 -0
  43. package/dist/oauth/index.js.map +1 -1
  44. package/dist/oauth/oauth-server.d.ts +61 -1
  45. package/dist/oauth/oauth-server.d.ts.map +1 -1
  46. package/dist/oauth/oauth-server.js +181 -40
  47. package/dist/oauth/oauth-server.js.map +1 -1
  48. package/dist/oauth/persistent-client-store.d.ts +58 -0
  49. package/dist/oauth/persistent-client-store.d.ts.map +1 -0
  50. package/dist/oauth/persistent-client-store.js +187 -0
  51. package/dist/oauth/persistent-client-store.js.map +1 -0
  52. package/dist/oauth/persistent-refresh-token-store.d.ts +77 -0
  53. package/dist/oauth/persistent-refresh-token-store.d.ts.map +1 -0
  54. package/dist/oauth/persistent-refresh-token-store.js +225 -0
  55. package/dist/oauth/persistent-refresh-token-store.js.map +1 -0
  56. package/dist/oauth/persistent-session-store.d.ts +69 -0
  57. package/dist/oauth/persistent-session-store.d.ts.map +1 -0
  58. package/dist/oauth/persistent-session-store.js +154 -0
  59. package/dist/oauth/persistent-session-store.js.map +1 -0
  60. package/dist/oauth/session-store.d.ts +31 -0
  61. package/dist/oauth/session-store.d.ts.map +1 -0
  62. package/dist/oauth/session-store.js +47 -0
  63. package/dist/oauth/session-store.js.map +1 -0
  64. package/dist/services/working-cadence.d.ts +37 -1
  65. package/dist/services/working-cadence.d.ts.map +1 -1
  66. package/dist/services/working-cadence.js +151 -13
  67. package/dist/services/working-cadence.js.map +1 -1
  68. package/dist/tools/calendar/handlers.d.ts +82 -3
  69. package/dist/tools/calendar/handlers.d.ts.map +1 -1
  70. package/dist/tools/calendar/handlers.js +200 -16
  71. package/dist/tools/calendar/handlers.js.map +1 -1
  72. package/dist/types/google-calendar-types.d.ts +150 -3
  73. package/dist/types/google-calendar-types.d.ts.map +1 -1
  74. package/dist/types/google-calendar-types.js +79 -2
  75. package/dist/types/google-calendar-types.js.map +1 -1
  76. package/dist/types/task.d.ts +14 -0
  77. package/dist/types/task.d.ts.map +1 -1
  78. package/package.json +1 -1
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Persistent Session Store
3
+ * Requirements: 26 (Session Management)
4
+ *
5
+ * Stores user sessions with encrypted filesystem persistence.
6
+ * Implements automatic cleanup of expired sessions and session limits.
7
+ */
8
+ import { UserSession } from './types.js';
9
+ import { EncryptionService } from './encryption-service.js';
10
+ import { SessionStore } from './session-store.js';
11
+ /**
12
+ * Persistent Session Store Implementation
13
+ *
14
+ * Extends SessionStore interface with encrypted filesystem persistence.
15
+ * Maintains in-memory cache for fast access while persisting to disk.
16
+ */
17
+ export declare class PersistentSessionStore implements SessionStore {
18
+ private sessions;
19
+ private sessionExpiryMs;
20
+ private maxSessions;
21
+ private encryptionService;
22
+ private storagePath;
23
+ constructor(encryptionService: EncryptionService, storagePath?: string);
24
+ /**
25
+ * Load sessions from encrypted file
26
+ *
27
+ * Filters out expired sessions during load.
28
+ * Logs loaded and expired session counts.
29
+ */
30
+ loadFromStorage(): Promise<void>;
31
+ /**
32
+ * Save sessions to encrypted file
33
+ *
34
+ * Enforces session limit by keeping only most recent 100 sessions.
35
+ */
36
+ private saveToStorage;
37
+ /**
38
+ * Create new session
39
+ *
40
+ * Saves asynchronously (fire and forget) to avoid blocking.
41
+ */
42
+ createSession(userId: string): UserSession;
43
+ /**
44
+ * Get session by ID
45
+ *
46
+ * Checks expiry and removes expired sessions automatically.
47
+ */
48
+ getSession(sessionId: string): UserSession | null;
49
+ /**
50
+ * Delete session
51
+ *
52
+ * Saves asynchronously after deletion.
53
+ */
54
+ deleteSession(sessionId: string): void;
55
+ /**
56
+ * Flush pending saves
57
+ *
58
+ * Call on server shutdown to ensure all data is persisted.
59
+ */
60
+ flush(): Promise<void>;
61
+ /**
62
+ * Get metrics for monitoring
63
+ */
64
+ getMetrics(): {
65
+ count: number;
66
+ expiredCount: number;
67
+ };
68
+ }
69
+ //# sourceMappingURL=persistent-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistent-session-store.d.ts","sourceRoot":"","sources":["../../src/oauth/persistent-session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD;;;;;GAKG;AACH,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,WAAW,CAAS;gBAEhB,iBAAiB,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,MAAM;IAKtE;;;;;OAKG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAgCtC;;;;OAIG;YACW,aAAa;IAuB3B;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW;IAmB1C;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAgBjD;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAOtC;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,UAAU,IAAI;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;KACtB;CAeF"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Persistent Session Store
3
+ * Requirements: 26 (Session Management)
4
+ *
5
+ * Stores user sessions with encrypted filesystem persistence.
6
+ * Implements automatic cleanup of expired sessions and session limits.
7
+ */
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ import { randomBytes } from 'crypto';
11
+ /**
12
+ * Persistent Session Store Implementation
13
+ *
14
+ * Extends SessionStore interface with encrypted filesystem persistence.
15
+ * Maintains in-memory cache for fast access while persisting to disk.
16
+ */
17
+ export class PersistentSessionStore {
18
+ sessions = new Map();
19
+ sessionExpiryMs = 24 * 60 * 60 * 1000; // 24 hours
20
+ maxSessions = 100; // Limit to prevent unbounded growth
21
+ encryptionService;
22
+ storagePath;
23
+ constructor(encryptionService, storagePath) {
24
+ this.encryptionService = encryptionService;
25
+ this.storagePath = storagePath || join(homedir(), '.sage', 'oauth_sessions.enc');
26
+ }
27
+ /**
28
+ * Load sessions from encrypted file
29
+ *
30
+ * Filters out expired sessions during load.
31
+ * Logs loaded and expired session counts.
32
+ */
33
+ async loadFromStorage() {
34
+ const data = await this.encryptionService.decryptFromFile(this.storagePath);
35
+ if (!data) {
36
+ console.log('[OAuth] No existing sessions found, starting fresh');
37
+ return;
38
+ }
39
+ try {
40
+ const storage = JSON.parse(data);
41
+ // Load sessions and filter expired ones
42
+ const now = Date.now();
43
+ let loadedCount = 0;
44
+ let expiredCount = 0;
45
+ for (const session of storage.sessions) {
46
+ if (now < session.expiresAt) {
47
+ this.sessions.set(session.sessionId, session);
48
+ loadedCount++;
49
+ }
50
+ else {
51
+ expiredCount++;
52
+ }
53
+ }
54
+ console.log(`[OAuth] Loaded ${loadedCount} sessions (${expiredCount} expired sessions cleaned up)`);
55
+ }
56
+ catch (error) {
57
+ console.error('[OAuth] Failed to parse session storage, starting fresh:', error);
58
+ }
59
+ }
60
+ /**
61
+ * Save sessions to encrypted file
62
+ *
63
+ * Enforces session limit by keeping only most recent 100 sessions.
64
+ */
65
+ async saveToStorage() {
66
+ // Enforce session limit by keeping only most recent sessions
67
+ let sessions = Array.from(this.sessions.values());
68
+ if (sessions.length > this.maxSessions) {
69
+ sessions.sort((a, b) => b.createdAt - a.createdAt);
70
+ sessions = sessions.slice(0, this.maxSessions);
71
+ // Update map to reflect limit
72
+ this.sessions.clear();
73
+ for (const session of sessions) {
74
+ this.sessions.set(session.sessionId, session);
75
+ }
76
+ }
77
+ const storage = {
78
+ version: 1,
79
+ sessions,
80
+ };
81
+ const data = JSON.stringify(storage, null, 2);
82
+ await this.encryptionService.encryptToFile(data, this.storagePath);
83
+ }
84
+ /**
85
+ * Create new session
86
+ *
87
+ * Saves asynchronously (fire and forget) to avoid blocking.
88
+ */
89
+ createSession(userId) {
90
+ const sessionId = randomBytes(32).toString('hex');
91
+ const now = Date.now();
92
+ const session = {
93
+ sessionId,
94
+ userId,
95
+ createdAt: now,
96
+ expiresAt: now + this.sessionExpiryMs,
97
+ };
98
+ this.sessions.set(sessionId, session);
99
+ // Save asynchronously (don't wait)
100
+ this.saveToStorage().catch((err) => console.error('[OAuth] Failed to save session:', err));
101
+ return session;
102
+ }
103
+ /**
104
+ * Get session by ID
105
+ *
106
+ * Checks expiry and removes expired sessions automatically.
107
+ */
108
+ getSession(sessionId) {
109
+ const session = this.sessions.get(sessionId);
110
+ if (!session)
111
+ return null;
112
+ // Check expiry
113
+ if (Date.now() > session.expiresAt) {
114
+ this.sessions.delete(sessionId);
115
+ this.saveToStorage().catch((err) => console.error('[OAuth] Failed to save after session expiry:', err));
116
+ return null;
117
+ }
118
+ return session;
119
+ }
120
+ /**
121
+ * Delete session
122
+ *
123
+ * Saves asynchronously after deletion.
124
+ */
125
+ deleteSession(sessionId) {
126
+ this.sessions.delete(sessionId);
127
+ this.saveToStorage().catch((err) => console.error('[OAuth] Failed to save after session deletion:', err));
128
+ }
129
+ /**
130
+ * Flush pending saves
131
+ *
132
+ * Call on server shutdown to ensure all data is persisted.
133
+ */
134
+ async flush() {
135
+ await this.saveToStorage();
136
+ }
137
+ /**
138
+ * Get metrics for monitoring
139
+ */
140
+ getMetrics() {
141
+ const now = Date.now();
142
+ let expiredCount = 0;
143
+ for (const session of this.sessions.values()) {
144
+ if (session.expiresAt < now) {
145
+ expiredCount++;
146
+ }
147
+ }
148
+ return {
149
+ count: this.sessions.size,
150
+ expiredCount,
151
+ };
152
+ }
153
+ }
154
+ //# sourceMappingURL=persistent-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistent-session-store.js","sourceRoot":"","sources":["../../src/oauth/persistent-session-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Session Store
3
+ * Requirements: 26 (Session Management)
4
+ *
5
+ * Manages user sessions for OAuth authentication flow.
6
+ * Sessions track authenticated users during the consent process.
7
+ */
8
+ import { UserSession } from './types.js';
9
+ /**
10
+ * Session Store Interface
11
+ */
12
+ export interface SessionStore {
13
+ createSession(userId: string): UserSession;
14
+ getSession(sessionId: string): UserSession | null;
15
+ deleteSession(sessionId: string): void;
16
+ }
17
+ /**
18
+ * In-memory Session Store Implementation
19
+ */
20
+ export declare class InMemorySessionStore implements SessionStore {
21
+ private sessions;
22
+ private sessionExpiryMs;
23
+ createSession(userId: string): UserSession;
24
+ getSession(sessionId: string): UserSession | null;
25
+ deleteSession(sessionId: string): void;
26
+ }
27
+ /**
28
+ * Create a Session Store instance
29
+ */
30
+ export declare function createSessionStore(): SessionStore;
31
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/oauth/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAClD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,eAAe,CAAuB;IAE9C,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW;IAa1C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAUjD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAGvC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,CAEjD"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Session Store
3
+ * Requirements: 26 (Session Management)
4
+ *
5
+ * Manages user sessions for OAuth authentication flow.
6
+ * Sessions track authenticated users during the consent process.
7
+ */
8
+ import { randomBytes } from 'crypto';
9
+ /**
10
+ * In-memory Session Store Implementation
11
+ */
12
+ export class InMemorySessionStore {
13
+ sessions = new Map();
14
+ sessionExpiryMs = 24 * 60 * 60 * 1000; // 24 hours
15
+ createSession(userId) {
16
+ const sessionId = randomBytes(32).toString('hex');
17
+ const now = Date.now();
18
+ const session = {
19
+ sessionId,
20
+ userId,
21
+ createdAt: now,
22
+ expiresAt: now + this.sessionExpiryMs,
23
+ };
24
+ this.sessions.set(sessionId, session);
25
+ return session;
26
+ }
27
+ getSession(sessionId) {
28
+ const session = this.sessions.get(sessionId);
29
+ if (!session)
30
+ return null;
31
+ if (Date.now() > session.expiresAt) {
32
+ this.sessions.delete(sessionId);
33
+ return null;
34
+ }
35
+ return session;
36
+ }
37
+ deleteSession(sessionId) {
38
+ this.sessions.delete(sessionId);
39
+ }
40
+ }
41
+ /**
42
+ * Create a Session Store instance
43
+ */
44
+ export function createSessionStore() {
45
+ return new InMemorySessionStore();
46
+ }
47
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/oauth/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAYrC;;GAEG;AACH,MAAM,OAAO,oBAAoB;IACvB,QAAQ,GAA6B,IAAI,GAAG,EAAE,CAAC;IAC/C,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW;IAE1D,aAAa,CAAC,MAAc;QAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAgB;YAC3B,SAAS;YACT,MAAM;YACN,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG,GAAG,IAAI,CAAC,eAAe;SACtC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;YACnC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,aAAa,CAAC,SAAiB;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,oBAAoB,EAAE,CAAC;AACpC,CAAC"}
@@ -8,6 +8,7 @@
8
8
  * - Scheduling recommendations
9
9
  */
10
10
  import type { CalendarConfig, DeepWorkBlock } from '../types/config.js';
11
+ import type { CalendarSourceManager } from '../integrations/calendar-source-manager.js';
11
12
  export interface GetWorkingCadenceRequest {
12
13
  /** Get info for a specific day of week */
13
14
  dayOfWeek?: 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday';
@@ -55,6 +56,18 @@ export interface WorkingCadenceResult {
55
56
  recommendations: SchedulingRecommendation[];
56
57
  /** Generated summary message */
57
58
  summary: string;
59
+ /** Focus time statistics from calendar events (optional) */
60
+ focusTimeStats?: {
61
+ /** Total focus time blocks by day of week */
62
+ focusTimeBlocks: Array<{
63
+ day: string;
64
+ duration: number;
65
+ }>;
66
+ /** Days detected as deep work from focusTime events (>=4h) */
67
+ detectedDeepWorkDays: string[];
68
+ /** Whether deep work days were enhanced by focusTime analysis */
69
+ enhanced: boolean;
70
+ };
58
71
  }
59
72
  export interface DeepWorkBlockInfo {
60
73
  day: string;
@@ -73,6 +86,12 @@ export interface SchedulingRecommendation {
73
86
  reason: string;
74
87
  }
75
88
  export declare class WorkingCadenceService {
89
+ private calendarSourceManager?;
90
+ /**
91
+ * Constructor
92
+ * @param calendarSourceManager Optional CalendarSourceManager for focusTime analysis
93
+ */
94
+ constructor(calendarSourceManager?: CalendarSourceManager);
76
95
  /**
77
96
  * Get working cadence information
78
97
  */
@@ -87,8 +106,10 @@ export declare class WorkingCadenceService {
87
106
  getDayOfWeek(date: string): string;
88
107
  /**
89
108
  * Generate scheduling recommendations based on calendar config
109
+ * @param config Calendar configuration
110
+ * @param focusTimeStats Optional focus time statistics for enhanced recommendations
90
111
  */
91
- generateRecommendations(config: CalendarConfig): SchedulingRecommendation[];
112
+ generateRecommendations(config: CalendarConfig, focusTimeStats?: WorkingCadenceResult['focusTimeStats']): SchedulingRecommendation[];
92
113
  /**
93
114
  * Calculate working hours info from config
94
115
  */
@@ -115,5 +136,20 @@ export declare class WorkingCadenceService {
115
136
  private formatHour;
116
137
  private isValidDate;
117
138
  private errorResult;
139
+ /**
140
+ * Analyze focus time events from calendar
141
+ * Filters events with eventType='focusTime' and calculates total focus time per day
142
+ * @param events Calendar events to analyze
143
+ * @returns Object containing focus time blocks with day and duration
144
+ */
145
+ private analyzeFocusTimeEvents;
146
+ /**
147
+ * Enhance deep work detection by combining config settings with focus time analysis
148
+ * Days with >=4h (240 minutes) of focusTime events are considered deep work days
149
+ * @param configDeepWorkDays Deep work days from configuration
150
+ * @param focusTimeBlocks Focus time blocks from calendar analysis
151
+ * @returns Combined list of deep work days (unique values)
152
+ */
153
+ private enhanceDeepWorkDetection;
118
154
  }
119
155
  //# sourceMappingURL=working-cadence.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"working-cadence.d.ts","sourceRoot":"","sources":["../../src/services/working-cadence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"working-cadence.d.ts","sourceRoot":"","sources":["../../src/services/working-cadence.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,14 @@ const DAY_MAP = {
20
20
  Sunday: '日',
21
21
  };
22
22
  export class WorkingCadenceService {
23
+ calendarSourceManager;
24
+ /**
25
+ * Constructor
26
+ * @param calendarSourceManager Optional CalendarSourceManager for focusTime analysis
27
+ */
28
+ constructor(calendarSourceManager) {
29
+ this.calendarSourceManager = calendarSourceManager;
30
+ }
23
31
  /**
24
32
  * Get working cadence information
25
33
  */
@@ -46,20 +54,56 @@ export class WorkingCadenceService {
46
54
  }
47
55
  // Calculate working hours
48
56
  const workingHours = this.calculateWorkingHours(config.calendar.workingHours);
49
- // Build weekly pattern
50
- const weeklyPattern = this.buildWeeklyPattern(config.calendar);
57
+ // Load calendar events and analyze focusTime (if CalendarSourceManager is available)
58
+ let focusTimeStats;
59
+ let enhancedDeepWorkDays = config.calendar.deepWorkDays;
60
+ if (this.calendarSourceManager) {
61
+ try {
62
+ // Get events from the past 4 weeks for better weekly pattern analysis
63
+ const now = new Date();
64
+ const startDate = new Date(now);
65
+ startDate.setDate(startDate.getDate() - 28);
66
+ const endDate = new Date(now);
67
+ endDate.setDate(endDate.getDate() + 7); // Include next week for upcoming focusTime
68
+ const events = await this.calendarSourceManager.getEvents(startDate.toISOString(), endDate.toISOString());
69
+ // Analyze focusTime events
70
+ const { focusTimeBlocks } = this.analyzeFocusTimeEvents(events);
71
+ // Enhance deep work detection with focusTime analysis
72
+ const detectedDeepWorkDays = focusTimeBlocks
73
+ .filter((block) => block.duration >= 240) // >=4 hours
74
+ .map((block) => block.day);
75
+ enhancedDeepWorkDays = this.enhanceDeepWorkDetection(config.calendar.deepWorkDays, focusTimeBlocks);
76
+ // Check if any new days were detected from focusTime
77
+ const enhanced = detectedDeepWorkDays.some((day) => !config.calendar.deepWorkDays.includes(day));
78
+ focusTimeStats = {
79
+ focusTimeBlocks,
80
+ detectedDeepWorkDays,
81
+ enhanced,
82
+ };
83
+ }
84
+ catch (error) {
85
+ // If calendar loading fails, continue without focusTime analysis
86
+ console.error('Failed to load calendar events for focusTime analysis:', error);
87
+ }
88
+ }
89
+ // Build weekly pattern with enhanced deep work days
90
+ const enhancedCalendarConfig = {
91
+ ...config.calendar,
92
+ deepWorkDays: enhancedDeepWorkDays,
93
+ };
94
+ const weeklyPattern = this.buildWeeklyPattern(enhancedCalendarConfig);
51
95
  // Transform deep work blocks
52
96
  const deepWorkBlocks = this.transformDeepWorkBlocks(config.calendar.deepWorkBlocks);
53
- // Generate recommendations
54
- const recommendations = this.generateRecommendations(config.calendar);
55
- // Build specific day info if requested
97
+ // Generate recommendations (with enhanced config for focusTime-aware recommendations)
98
+ const recommendations = this.generateRecommendations(enhancedCalendarConfig, focusTimeStats);
99
+ // Build specific day info if requested (using enhanced deep work days)
56
100
  let specificDay;
57
101
  if (request?.dayOfWeek || request?.date) {
58
102
  const dayOfWeek = request.dayOfWeek || this.getDayOfWeek(request.date);
59
- specificDay = this.buildSpecificDayInfo(dayOfWeek, request.date, config.calendar, deepWorkBlocks);
103
+ specificDay = this.buildSpecificDayInfo(dayOfWeek, request.date, enhancedCalendarConfig, deepWorkBlocks, focusTimeStats);
60
104
  }
61
- // Generate summary
62
- const summary = this.generateSummary(workingHours, weeklyPattern, config.reminders.weeklyReview);
105
+ // Generate summary (with focusTime info if available)
106
+ const summary = this.generateSummary(workingHours, weeklyPattern, config.reminders.weeklyReview, focusTimeStats);
63
107
  return {
64
108
  success: true,
65
109
  user: {
@@ -73,6 +117,7 @@ export class WorkingCadenceService {
73
117
  specificDay,
74
118
  recommendations,
75
119
  summary,
120
+ focusTimeStats,
76
121
  };
77
122
  }
78
123
  /**
@@ -97,15 +142,25 @@ export class WorkingCadenceService {
97
142
  }
98
143
  /**
99
144
  * Generate scheduling recommendations based on calendar config
145
+ * @param config Calendar configuration
146
+ * @param focusTimeStats Optional focus time statistics for enhanced recommendations
100
147
  */
101
- generateRecommendations(config) {
148
+ generateRecommendations(config, focusTimeStats) {
102
149
  const recommendations = [];
103
150
  if (config.deepWorkDays.length > 0) {
151
+ let reason = 'これらの日はDeep Work日として設定されており、集中作業に適しています';
152
+ // Enhance reason if days were detected from focusTime events
153
+ if (focusTimeStats?.enhanced && focusTimeStats.detectedDeepWorkDays.length > 0) {
154
+ const detectedDays = focusTimeStats.detectedDeepWorkDays.filter((day) => !config.deepWorkDays.includes(day));
155
+ if (detectedDays.length > 0) {
156
+ reason += ` (${this.formatDays(detectedDays)}はFocus Timeイベントから検出)`;
157
+ }
158
+ }
104
159
  recommendations.push({
105
160
  type: 'deep-work',
106
161
  recommendation: `複雑なタスクは${this.formatDays(config.deepWorkDays)}にスケジュールしてください`,
107
162
  bestDays: config.deepWorkDays,
108
- reason: 'これらの日はDeep Work日として設定されており、集中作業に適しています',
163
+ reason,
109
164
  });
110
165
  }
111
166
  if (config.meetingHeavyDays.length > 0) {
@@ -125,6 +180,17 @@ export class WorkingCadenceService {
125
180
  reason: 'ミーティング日の合間は短いタスクに適しています',
126
181
  });
127
182
  }
183
+ // Focus time recommendation if focusTime events exist
184
+ if (focusTimeStats && focusTimeStats.focusTimeBlocks.length > 0) {
185
+ const focusTimeDays = focusTimeStats.focusTimeBlocks.map((block) => block.day);
186
+ const uniqueFocusTimeDays = [...new Set(focusTimeDays)];
187
+ recommendations.push({
188
+ type: 'deep-work',
189
+ recommendation: `${this.formatDays(uniqueFocusTimeDays)}には既存のFocus Timeブロックがあります。この時間を活用してください`,
190
+ bestDays: uniqueFocusTimeDays,
191
+ reason: 'カレンダーにFocus Timeイベントが登録されています。集中作業に最適な時間です',
192
+ });
193
+ }
128
194
  return recommendations;
129
195
  }
130
196
  /**
@@ -171,7 +237,7 @@ export class WorkingCadenceService {
171
237
  normalDays,
172
238
  };
173
239
  }
174
- buildSpecificDayInfo(dayOfWeek, date, calendarConfig, allDeepWorkBlocks) {
240
+ buildSpecificDayInfo(dayOfWeek, date, calendarConfig, allDeepWorkBlocks, focusTimeStats) {
175
241
  const dayType = this.getDayType(dayOfWeek, calendarConfig);
176
242
  const dayBlocks = allDeepWorkBlocks.filter((block) => block.day === dayOfWeek);
177
243
  const recommendations = [];
@@ -181,12 +247,32 @@ export class WorkingCadenceService {
181
247
  const times = dayBlocks.map((b) => `${b.startTime}-${b.endTime}`).join(', ');
182
248
  recommendations.push(`Deep Workブロック: ${times}`);
183
249
  }
250
+ // Add focusTime info if detected from calendar events
251
+ if (focusTimeStats?.detectedDeepWorkDays.includes(dayOfWeek)) {
252
+ const focusTimeBlock = focusTimeStats.focusTimeBlocks.find((b) => b.day === dayOfWeek);
253
+ if (focusTimeBlock) {
254
+ const hours = Math.floor(focusTimeBlock.duration / 60);
255
+ const minutes = Math.round(focusTimeBlock.duration % 60);
256
+ const durationStr = minutes > 0 ? `${hours}時間${minutes}分` : `${hours}時間`;
257
+ recommendations.push(`Focus Timeイベント: 合計${durationStr}のFocus Timeが登録されています`);
258
+ }
259
+ }
184
260
  }
185
261
  else if (dayType === 'meeting-heavy') {
186
262
  recommendations.push('この日はミーティングが多い日です。ミーティングの合間に短いタスクを処理してください。');
187
263
  }
188
264
  else {
189
265
  recommendations.push('この日は特別な設定がありません。自由にスケジュールを組めます。');
266
+ // Check if there are focusTime blocks on this day
267
+ if (focusTimeStats) {
268
+ const focusTimeBlock = focusTimeStats.focusTimeBlocks.find((b) => b.day === dayOfWeek);
269
+ if (focusTimeBlock) {
270
+ const hours = Math.floor(focusTimeBlock.duration / 60);
271
+ const minutes = Math.round(focusTimeBlock.duration % 60);
272
+ const durationStr = minutes > 0 ? `${hours}時間${minutes}分` : `${hours}時間`;
273
+ recommendations.push(`Focus Timeイベント: 合計${durationStr}のFocus Timeが登録されています`);
274
+ }
275
+ }
190
276
  }
191
277
  return {
192
278
  date,
@@ -196,11 +282,16 @@ export class WorkingCadenceService {
196
282
  recommendations,
197
283
  };
198
284
  }
199
- generateSummary(workingHours, weeklyPattern, weeklyReview) {
285
+ generateSummary(workingHours, weeklyPattern, weeklyReview, focusTimeStats) {
200
286
  const lines = [];
201
287
  lines.push(`勤務時間: ${workingHours.start}-${workingHours.end} (${Math.floor(workingHours.totalMinutes / 60)}時間)`);
202
288
  if (weeklyPattern.deepWorkDays.length > 0) {
203
- lines.push(`Deep Work日: ${this.formatDays(weeklyPattern.deepWorkDays)}`);
289
+ let deepWorkLine = `Deep Work日: ${this.formatDays(weeklyPattern.deepWorkDays)}`;
290
+ // Add indicator if days were enhanced by focusTime analysis
291
+ if (focusTimeStats?.enhanced) {
292
+ deepWorkLine += ' (Focus Time分析により強化)';
293
+ }
294
+ lines.push(deepWorkLine);
204
295
  }
205
296
  if (weeklyPattern.meetingHeavyDays.length > 0) {
206
297
  lines.push(`ミーティング集中日: ${this.formatDays(weeklyPattern.meetingHeavyDays)}`);
@@ -208,6 +299,14 @@ export class WorkingCadenceService {
208
299
  if (weeklyReview?.enabled) {
209
300
  lines.push(`週次レビュー: ${DAY_MAP[weeklyReview.day] || weeklyReview.day}曜 ${weeklyReview.time}`);
210
301
  }
302
+ // Add focusTime summary if available
303
+ if (focusTimeStats && focusTimeStats.focusTimeBlocks.length > 0) {
304
+ const totalFocusMinutes = focusTimeStats.focusTimeBlocks.reduce((sum, block) => sum + block.duration, 0);
305
+ const hours = Math.floor(totalFocusMinutes / 60);
306
+ const minutes = Math.round(totalFocusMinutes % 60);
307
+ const durationStr = minutes > 0 ? `${hours}時間${minutes}分` : `${hours}時間`;
308
+ lines.push(`Focus Time: 週合計${durationStr}`);
309
+ }
211
310
  return lines.join('\n');
212
311
  }
213
312
  timeToMinutes(time) {
@@ -233,5 +332,44 @@ export class WorkingCadenceService {
233
332
  summary: '',
234
333
  };
235
334
  }
335
+ /**
336
+ * Analyze focus time events from calendar
337
+ * Filters events with eventType='focusTime' and calculates total focus time per day
338
+ * @param events Calendar events to analyze
339
+ * @returns Object containing focus time blocks with day and duration
340
+ */
341
+ analyzeFocusTimeEvents(events) {
342
+ // Filter events with eventType='focusTime'
343
+ const focusTimeEvents = events.filter((e) => e.eventType === 'focusTime');
344
+ // Calculate total focus time per day
345
+ const focusTimeByDay = new Map();
346
+ for (const event of focusTimeEvents) {
347
+ const day = new Date(event.start).toLocaleDateString('en-US', { weekday: 'long' });
348
+ const duration = (new Date(event.end).getTime() - new Date(event.start).getTime()) / (1000 * 60); // minutes
349
+ focusTimeByDay.set(day, (focusTimeByDay.get(day) || 0) + duration);
350
+ }
351
+ // Return focus time statistics
352
+ const focusTimeBlocks = Array.from(focusTimeByDay.entries()).map(([day, duration]) => ({
353
+ day,
354
+ duration,
355
+ }));
356
+ return { focusTimeBlocks };
357
+ }
358
+ /**
359
+ * Enhance deep work detection by combining config settings with focus time analysis
360
+ * Days with >=4h (240 minutes) of focusTime events are considered deep work days
361
+ * @param configDeepWorkDays Deep work days from configuration
362
+ * @param focusTimeBlocks Focus time blocks from calendar analysis
363
+ * @returns Combined list of deep work days (unique values)
364
+ */
365
+ enhanceDeepWorkDetection(configDeepWorkDays, focusTimeBlocks) {
366
+ // Days with >=4h (240 minutes) of focusTime events are considered deep work days
367
+ const focusTimeDays = focusTimeBlocks
368
+ .filter((block) => block.duration >= 240)
369
+ .map((block) => block.day);
370
+ // Combine config.deepWorkDays with focusTime analysis (remove duplicates)
371
+ const allDeepWorkDays = new Set([...configDeepWorkDays, ...focusTimeDays]);
372
+ return Array.from(allDeepWorkDays);
373
+ }
236
374
  }
237
375
  //# sourceMappingURL=working-cadence.js.map